##############################################################################
##
#W  gpdmor.gi                  Gpd Package                       Chris Wensley
#W                                                                & Emma Moore
##
##  This file contains generic methods for groupoids
##
#H  @(#)$Id: gpdmor.gi,v 1.01 2006/05/17 gap Exp $
##
#Y  Copyright (C) 1999,2001 University of St. Andrews, North Haugh,
#Y                          St. Andrews, Fife KY16 9SS, Scotland
##
Revision.("gpd/gap/gpdmor_gi") := 
   "@(#)$Id: gpdmor.gi,v 1.01 2006/05/17 gap Exp $";

#############################################################################
##  Standard error messages

GROUPOID_MORPHISM_CONSTRUCTORS := Concatenation(
    "The standard operations which construct a groupoid morphism are:\n",
    "1.  MorphismOfConnectedGroupoids( gpd1, gpd2, [imobs], gphom );\n", 
    "2.  MorphismToConnectedGroupoid( gpd1, gpd2, list of [[imobs],hom] );\n",
    "3.  GroupoidMorphismWithCommonRange( gpd1, gpd2, list of morphisms );\n",
    "4.  GroupoidMorphismByComponents( gpd1, gpd2, list of morphisms );\n", 
    "5.  GroupoidMorphismByUnion( list of disjoint morphisms );\n", 
    "6.  GroupoidMorphism( one of the previous parameter options );" );

#############################################################################
##
#F  GroupoidMorphism( <comps> )                            union of morphisms
#F  GroupoidMorphism( <g1>,<g2>,<mors> )                   list of components
#F  GroupoidMorphism( <g1>,<g2>,<[imo,hom]s> )                connected range
#F  GroupoidMorphism( <g1>,<g2>,<imo>,<hom> )                connected source
##
InstallGlobalFunction( GroupoidMorphism, function( arg )

    local  nargs, id, rays;
    nargs := Length( arg );

    # list of components
    if ( ( nargs = 1 ) and IsList( arg[1] ) and 
           ForAll( arg[1], m -> IsGroupoidMorphism( m ) ) ) then 
        Info( InfoGpd, 2, "GroupoidMorphismByUnion" );
        return GroupoidMorphismByUnion( arg[1] );
    elif ( ( nargs < 3 ) or not IsGroupoid( arg[1] )
                         or not IsGroupoid( arg[2] ) ) then 
        Info( InfoGpd, 1, GROUPOID_MORPHISM_CONSTRUCTORS );
        return fail;
    fi;
    # groupoid, groupoid, object images, homomorphism
    if ( ( nargs = 4 ) and IsConnectedGroupoid( arg[1] ) 
                       and IsHomogeneousList( arg[3] ) 
                       and IsGroupHomomorphism( arg[4] ) ) then 
        Info( InfoGpd, 2, "MorphismOfConnectedGroupoids" );
        return MorphismOfConnectedGroupoids( arg[1], arg[2], arg[3], arg[4] );
    # groupoid. groupoid, list of morphisms
    elif ( ( nargs = 3 ) and IsHomogeneousList( arg[3] ) 
            and IsGroupoidMorphism( arg[3][1] ) ) then
        Info( InfoGpd, 2, "GroupoidMorphismByComponents" );
        return GroupoidMorphismByComponents( arg[1], arg[2], arg[3] );
    # groupoid, groupoid, list of [[images of objects],homomorphism] paris
    elif ( ( nargs = 3 ) and IsHomogeneousList( arg[3] ) 
           and IsList( arg[3][1] ) and IsList( arg[3][1][1] ) ) then 
        Info( InfoGpd, 2, "MorphismToConnectedGroupoid" );
        return MorphismToConnectedGroupoid( arg[1], arg[2], arg[3] );
    else
        Info( InfoGpd, 1, GROUPOID_MORPHISM_CONSTRUCTORS );
        return fail;
    fi;
end );

#############################################################################
##
#M  MorphismToConnectedGroupoidNC  
#M  MorphismToConnectedGroupoid
##
InstallMethod( MorphismToConnectedGroupoidNC,
    "generic method for a morphism to a connected groupoid", true,
    [ IsGroupoid, IsConnectedGroupoid, IsHomogeneousList ], 0,
function( gpd1, gpd2, obhoms )

    local  fam, filter, mor, inj, sur, imo, homs;

    fam := FamilyObj( [ gpd1, gpd2, obhoms ] );
    filter := IsMorphismToConnectedGroupoidRep;
    mor := Objectify( NewType( fam, filter ), rec () );
    SetSource( mor, gpd1 );
    SetRange( mor, gpd2 );
    SetComponentImages( mor, obhoms );
    SetIsMorphismToConnectedGroupoid( mor, true );
    inj := IsInjectiveOnObjects( mor ); 
    sur := IsSurjectiveOnObjects( mor );
    return mor; 
end );

InstallMethod( MorphismToConnectedGroupoid,
    "generic method for a morphism to a connected groupoid", true,
    [ IsGroupoid, IsConnectedGroupoid, IsHomogeneousList ], 0,
function( g1, g2, obhoms )

    local  comp, o2, gp2, j, c, h, mor, obs1;

    Info( InfoGpd, 2, "Morphism to connected groupoid:", obhoms );
    comp := ComponentsOfGroupoid( g1 );
    obs1 := Flat( List( comp, g -> g!.objects ) );
    if not IsDuplicateFree( obs1 ) then
        Error( "morphisms do not have disjoint source groupoids" );
    fi;
    o2 := g2!.objects;
    gp2 := g2!.group;
    for j in [1..Length(comp)] do
        c := comp[j];
        if not ( Length( obhoms[j][1] ) = Length( c!.objects ) ) then
            Error( "incorrect length for images of objects" );
        fi;
        if not IsSubset( o2, obhoms[j][1] ) then
            Error( "object images not in the objects of g2" );
        fi;
        h := obhoms[j][2];
        if not IsGroupHomomorphism( h ) then
            Error( "obhoms[[j][2] is not a group homomorphism" );
        fi;
        if not ( ( Source(h) = c!.group ) and ( Range(h) = gp2 ) ) then
            Error( "homs not a homomorphism g1!.group -> g2!.group" );
        fi;
    od;
    return MorphismToConnectedGroupoidNC( g1, g2, obhoms );
end );

#############################################################################
##
#M  MorphismOfConnectedGroupoids
##
InstallMethod( MorphismOfConnectedGroupoids,
    "generic method for a morphism of connected groupoids", true,
    [ IsConnectedGroupoid, IsConnectedGroupoid, IsHomogeneousList, 
      IsGroupHomomorphism ], 0,
function( g1, g2, imo, hom )

    local  o1, o2, no2, gp1, gp2;

    o1 := g1!.objects;
    o2 := g2!.objects;
    no2 := Length( o2 );
    if not ( Length( imo ) = Length( o1 ) ) then
        Error( "object images of incorrect length" );
    fi;
    if not IsSubset( o2, imo ) then
        Error( "object images not in the objects of g2" );
    fi;
    gp2 := g2!.group;
    gp1 := g1!.group; 
    if not ( ( Source(hom) = gp1 ) and ( Range(hom) = gp2 ) ) then
        Error( "hom not a homomorphism g1!.group -> g2!.group" );
    fi;
    return MorphismToConnectedGroupoidNC( g1, g2, [ [imo,hom] ] );
end );

############################################################################# 
##
#M  GroupoidMorphismWithCommonRange
##
InstallMethod( GroupoidMorphismWithCommonRange,
    "for a list of groupoid morphisms with common connected range", true,
    [ IsGroupoid, IsConnectedGroupoid, IsHomogeneousList ], 0,  
function( gpd1, gpd2, mors )

    local  m, obhoms, i, len;

    len := Length( mors );
    obhoms := [ ];
    for i in [1..len] do
        m := mors[i];
        if not ( IsMorphismToConnectedGroupoid( m ) 
                 and IsSubgroupoid( gpd2, Range( m ) ) ) then
            Error( "not all morphisms have image in gpd2" );
        fi;
        Append( obhoms , ComponentImages( m ) );
    od;
    return MorphismToConnectedGroupoid( gpd1, gpd2, obhoms );
end );

#############################################################################
##
#M  GroupoidMorphismByComponentsNC 
#M  GroupoidMorphismByComponents
##
InstallMethod( GroupoidMorphismByComponentsNC, 
    "generic method for a groupoid morphism", true,
    [ IsGroupoid, IsGroupoid, IsHomogeneousList ], 0,  ##was IsGroupoidRep
function( gpd1, gpd2, mors )

    local  fam, filter, mor, inj, comp1, nc1, comp2, nc2, obs1, 
           part, i, m, src;

    if IsConnectedGroupoid( gpd2 ) then
        Info( InfoGpd, 3, "transferring to CommonRange function" );
        return GroupoidMorphismWithCommonRange( gpd1, gpd2, mors );
    fi;
    fam := FamilyObj( [ gpd1, gpd2, mors ] );
    filter := IsGroupoidMorphismRep;
    mor := Objectify( NewType( fam, filter ), rec () );
    SetSource( mor, gpd1 );
    SetRange( mor, gpd2 );
    SetComponentsOfMorphism( mor, mors );
    SetIsMorphismToConnectedGroupoid( mor, false );
    inj := IsInjectiveOnObjects( mor ); 
    ## added 10/05/06
    comp1 := ComponentsOfGroupoid( gpd1 );
    nc1 := Length( comp1 );
    comp2 := ComponentsOfGroupoid( gpd2 );
    nc2 := Length( comp2 );
    obs1 := List( comp1, g -> g!.objects[1] );
    part := ListWithIdenticalEntries( nc2, 0 );
    for i in [1..nc2] do
        m := mors[i];
        src := Source( m );
        if IsConnectedGroupoid( src ) then
            part[i] := [ Position( obs1, src!.objects[1] ) ];
        else
            part[i] := List( ComponentsOfGroupoid( src ),  
                       c -> Position( obs1, c!.objects[1] ) );
        fi;
    od;
    Info( InfoGpd, 3, "part = ", part );
    SetPartitionOfSource( mor, part );
    return mor; 
end );

InstallMethod( GroupoidMorphismByComponents, 
    "generic method for a groupoid morphism", true,
    [ IsGroupoid, IsGroupoid, IsHomogeneousList ], 0,  ## was IsGroupoidRep
function( gpd1, gpd2, mors )

    local  comp1, nc1, comp2, nc2, lenmors, lencomp, ncomp, expand, src1, g, 
           flat1, pos1, pos2, compmor, L, i, j, k, m, filt, morj, src;

    if IsConnectedGroupoid( gpd2 ) then
        Info( InfoGpd, 2, "transferring to CommonRange function" );
        return GroupoidMorphismWithCommonRange( gpd1, gpd2, mors );
    fi;
    comp1 := ComponentsOfGroupoid( gpd1 );
    nc1 := Length( comp1 );
    comp2 := ComponentsOfGroupoid( gpd2 );
    nc2 := Length( comp2 );
    lenmors := Length( mors );
    lencomp := ListWithIdenticalEntries( lenmors, 0 );
    for i in [1..lenmors] do 
        m := mors[i];
        if not HasComponentsOfMorphism( m ) then 
            lencomp[i] := 1; 
        else 
            lencomp[i] := Length( ComponentsOfMorphism( m ) );
        fi;
    od;
    ncomp := Sum( lencomp );    
    expand := ListWithIdenticalEntries( ncomp, 0 );
    j := 0;
    for i in [1..Length(mors)] do
        m := mors[i];
        if HasComponentsOfMorphism( m ) then
            compmor := ComponentsOfMorphism( m );
            for k in [1..Length( compmor )] do
                j := j+1;
                expand[j] := compmor[k];
            od;
        else
            j := j+1;
            expand[j] := m;
        fi;
    od;
    Info( InfoGpd, 3, "expanded mors:", expand );
    pos1 := ListWithIdenticalEntries( lenmors, 0 );
    src1 := List( expand, m -> Source( m ) );
    for j in [1..lenmors] do
        g := src1[j];
        if IsConnectedGroupoid( g ) then
            pos1[j] := [ Position( comp1, g ) ];
        else
            pos1[j] := List( ComponentsOfGroupoid(g),
                              c -> Position( comp1, c ) );
        fi;
    od;
    flat1 := Flat( pos1 );
    Info( InfoGpd, 3, " pos1 = ", pos1 );
    Info( InfoGpd, 3, "flat1 = ", flat1 );
    if ( fail in flat1 ) then
        Error( "not all m have source in gpd1" );
    fi;
    if not ( Set( flat1 ) = [1..Length(comp1)] ) then
        Error( "components of gpd1 <> union of sources in mors" );
    fi;
    ##  more efficient to use ComponentNrOfObject here ??
    pos2 := List( mors, m -> Position( comp2, 
                      ComponentOfObject( gpd2, Range(m)!.objects[1] ) ) );
    if ( fail in pos2 ) then
        Error( "not all m have range in gpd2" );
    fi;
    if IsDuplicateFree( pos2 ) then
        ## reorder if necessary
        Info( InfoGpd, 2, "duplicate free case" );
        L := [1..nc2];
        SortParallel( pos2, L );
        compmor := List( L, j -> expand[j] );
        return GroupoidMorphismByComponentsNC( gpd1, gpd2, compmor );
    else
        ## construct the component morphisms
        compmor := ListWithIdenticalEntries( nc2, 0 );
        for j in [1..nc2] do
            filt := Filtered( pos2, i -> (i=j) );
            morj := List( filt, i -> mors[i] );
            if ( Length( filt ) = 1 ) then
                compmor[j] := morj[1];
            else
                if ( Length( filt ) = nc1 ) then
                    src := gpd1;
                else
                    src := GroupoidByUnion( List(filt), i -> Source(mors[i]) );
                fi;
                compmor[j] := GroupoidMorphismWithCommonRange
                                  ( src, comp2[j], morj );
            fi;
        od;
        return GroupoidMorphismByComponentsNC( gpd1, gpd2, compmor );
    fi;
end );

#############################################################################
##
#M  GroupoidMorphismByUnion
##
InstallMethod( GroupoidMorphismByUnion, 
    "generic method for a list of groupoid morphisms", true,
    [ IsHomogeneousList ], 0,
function( mors )

    local  src, rng; 

    if not ForAll( mors, m -> IsGroupoidMorphism( m ) ) then
        Error( "entries in list mors not all groupoid morphisms" );
    fi;
    src := GroupoidByUnion( List( mors, m -> Source( m ) ) );
    rng := GroupoidByUnion( List( mors, m -> Range( m ) ) );
    if ( ( src = fail ) or ( rng = fail ) ) then
        Error( "check that sources and ranges in mors are disjoint" );
    fi;
    return GroupoidMorphismByComponents( src, rng, mors );
end );

#############################################################################
##
#M  IsomorphismNewObjects
##
InstallMethod( IsomorphismNewObjects, "for a connected groupoid", true,
    [ IsConnectedGroupoid, IsHomogeneousList ], 0,
function( g1, ob2 )

    local  iso, ob1, gp, g2, id;

    ob1 := g1!.objects;
    if not ( Length(ob1) = Length(ob2) ) then
        Error( "object sets have different lengths" );
    fi;
    gp := g1!.group;
    g2 := ConnectedGroupoid( ShallowCopy( ob2 ), gp );
    id := IdentityMapping( gp );
    iso := MorphismOfConnectedGroupoids( g1, g2, ob2, id );
    SetIsInjective( iso, true );
    SetIsSurjective( iso, true );
    return iso;
end );

InstallMethod( IsomorphismNewObjects, "generic method for a groupoid", true,
    [ IsGroupoid, IsHomogeneousList ], 0,
function( g1, ob2 )

    local  isos, comp1, nc1, i, g2, iso;

    comp1 := ComponentsOfGroupoid( g1 );
    nc1 := Length( comp1 );
    if not ( Length(ob2) = nc1 ) then
        Error( "new list of lists of objects has incorrect length" );
    fi;
    isos := ListWithIdenticalEntries( nc1, 0 );
    for i in [1..nc1] do 
        isos[i] := IsomorphismNewObjects( comp1[i], ob2[i] );
    od;
    g2 := GroupoidByUnion( List( isos, m -> Range(m) ) );
    iso := GroupoidMorphismByComponents( g1, g2, isos );
    SetIsInjective( iso, true );
    SetIsSurjective( iso, true );
    return iso;
end );

#############################################################################
##
#M  IsomorphismPermGroupoid
##
InstallMethod( IsomorphismPermGroupoid, "for a connected groupoid", true,
    [ IsConnectedGroupoid ], 0,
function( g1 )

    local  iso, obs, g, g2p, p, g2;

    obs := g1!.objects;
    g := g1!.group;
    g2p := IsomorphismPermGroup( g );
    p := Image( g2p );
    g2 := ConnectedGroupoid( obs, p );
    iso := MorphismOfConnectedGroupoids( g1, g2, obs, g2p );
    SetIsInjective( iso, true );
    SetIsSurjective( iso, true );
    return iso;
end );

InstallMethod( IsomorphismPermGroupoid, "generic method for a groupoid", 
    true, [ IsGroupoid ], 0,
function( g1 )

    local  isos, comp1, nc1, i, g2, iso;

    comp1 := ComponentsOfGroupoid( g1 );
    nc1 := Length( comp1 );
    isos := ListWithIdenticalEntries( nc1, 0 );
    for i in [1..nc1] do 
        isos[i] := IsomorphismPermGroupoid( comp1[i] );
    od;
    g2 := GroupoidByUnion( List( isos, m -> Image(m) ) );
    iso := GroupoidMorphismByComponents( g1, g2, isos );
    SetIsInjective( iso, true );
    SetIsSurjective( iso, true );
    return iso;
end );

#############################################################################
##
#M  InclusionMappingGroupoids
##
InstallMethod( InclusionMappingGroupoids, "for a subgroupoid of a groupoid", 
    true, [ IsConnectedGroupoid, IsGroupoid ], 0,
function( gpd, sgpd )
    local  sobs, o1, c1, morphisms, comps, m, inc, mor;

    if not IsSubgroupoid( gpd, sgpd ) then
        Error( "arg[2] is not a subgroupoid of arg[1]" );
    fi;
    if IsConnectedGroupoid( sgpd ) then
        sobs := sgpd!.objects;
        if IsConnectedGroupoid( gpd ) then
            inc := InclusionMappingGroups( gpd!.group, sgpd!.group );
            if ( inc = fail ) then
                Error( "group homomorphism fails to include" );
            fi;
            return MorphismToConnectedGroupoidNC( sgpd, gpd, [ [sobs,inc] ] );
        else
            o1 := sgpd!.objects[1];
            c1 := ComponentOfObject( gpd, o1 );
            inc := InclusionMappingGroups( c1!.group, sgpd!.group );
            if ( inc = fail ) then
                Error( "group homomorphism fails to include" );
            fi;
            mor := MorphismToConnectedGroupoidNC( sgpd, c1, [ [sobs,inc] ] );
            return GroupoidMorphism( sgpd, gpd, [ mor ] );
        fi;
    else
        Info( InfoGpd, 2, "InclusionMapping, source not connected" );
        morphisms := List( ComponentsOfGroupoid( sgpd ),
                           c -> InclusionMappingGroupoids( gpd, c ) );
        return GroupoidMorphismByComponents( sgpd, gpd, morphisms );
    fi;
end );

InstallMethod( InclusionMappingGroupoids, "for a subgroupoid of a groupoid", 
    true, [ IsGroupoid, IsGroupoid ], 0,
function( gpd, sgpd )
    if not IsSubgroupoid( gpd, sgpd ) then
        Error( "arg[2] is not a subgroupoid of arg[1]" );
    fi;
    if not IsConnectedGroupoid( sgpd ) then
        Error( "not yet implemented when arg[2] is not connected" );
    fi;
end );

#############################################################################
##
#M  RestrictionMappingGroupoids
##
InstallMethod( RestrictionMappingGroupoids, "for a groupoid morphism", true,
    [ IsMorphismToConnectedGroupoid, IsGroupoid, IsGroupoid ], 0,
function( mor, U, V )

    local  src, rng, obhoms, hom, imo, obsU, compU, lenU, mors, j, sj, osj, 
           pos, orj, rj, rhom;

    src := Source( mor );
    if not IsConnectedGroupoid( src ) then
        Print( "not yet implemented for Source(mor) not connected\n" );
        return fail;
    fi;
    if not IsConnectedGroupoid( V ) then
        Print( "not yet implemented for V not connected\n" );
        return fail;
    fi;
    rng := Range( mor );
    if not ( IsSubgroupoid( src, U ) and IsSubgroupoid( rng, V ) ) then
        Error( "not a pair of subgroupoids" );
    fi;
    obhoms := ComponentImages( mor );
    imo := obhoms[1][1];
    hom := obhoms[1][2];
    obsU := ObjectsOfGroupoid( U );
    compU := ComponentsOfGroupoid( U );
    lenU := Length( compU );
    mors := ListWithIdenticalEntries( lenU, 0 );
    for j in [1..lenU] do
        sj := compU[j];
        osj := sj!.objects;
        orj := List( osj, o -> imo[Position(obsU,o)] );
        rhom := RestrictionMappingGroups( hom, sj!.group, rng!.group );
        if ( rhom = fail ) then
            Error( "group homomorphism fails to restrict" );
        fi;
        mors[j] := MorphismOfConnectedGroupoids( sj, rng, orj, rhom );
    od;
    return GroupoidMorphismByComponents( U, V, mors );
end );

##  this one not checked
InstallMethod( RestrictionMappingGroupoids, "for a groupoid morphism", true,
    [ IsGroupoidMorphism, IsGroupoid, IsGroupoid ], 0,
function( mor, ssrc, srng )

    local  comp, len, rcomp, j, m;

    comp := ComponentsOfMorphism( mor );
    len := Length( comp );
    rcomp := ListWithIdenticalEntries( len, 0 );
    for j in [1..len] do
        rcomp[j] := RestrictionMappingGroupoids( comp[j], ssrc, srng );
    od;
    return GroupoidMorphismByComponentsNC( Source(mor), Range(mor), rcomp );
end );

#############################################################################
##
#M  IdentityMapping
##
InstallOtherMethod( IdentityMapping, "for a groupoid", true, [ IsGroupoid ], 0,
function( gpd )

    local  comp, gp, gens, id, iso;

    if IsConnectedGroupoid( gpd ) then
        iso := MorphismToConnectedGroupoidNC
            ( gpd, gpd, [ [gpd!.objects, IdentityMapping(gpd!.group)] ] );
    else
        comp := List( ComponentsOfGroupoid( gpd ), c -> IdentityMapping(c) );
        iso := GroupoidMorphism( gpd, gpd, comp );
    fi;
    SetIsInjective( iso, true );
    SetIsSurjective( iso, true );
    return iso;
end );

#############################################################################
##
#M  ImagesOfObjects 
##
InstallMethod( ImagesOfObjects, "for a groupoid morphism", true,
    [ IsMorphismToConnectedGroupoid ], 0,
function( mor )

    local  obhoms, src, obs, ims, i, j, c, srcc, obc, imc, o, pos;

    if not HasComponentsOfMorphism( mor ) then
        obhoms := ComponentImages( mor );
        if ( Length( obhoms ) = 1 ) then
            return obhoms[1][1];
        else
            src := Source( mor );
            obs := ObjectsOfGroupoid( src );
            ims := ShallowCopy( obs );
            c := ComponentsOfGroupoid( src ); 
            for i in [1..Length(c)] do 
                obc := c[i]!.objects;
                imc := obhoms[i][1];
                for j in [1..Length(obc)] do
                    pos := Position( obs, obc[j] );
                    ims[pos] := imc[j];
                od;
            od;
            return ims;
        fi;
    else  ## HasComponentsOfMorphism
        src := Source( mor );
        obs := ObjectsOfGroupoid( src );
        ims := ShallowCopy( obs );
        for c in ComponentsOfMorphism( mor ) do
            obc := Source(c)!.objects;
            for i in [1..Length(obc)] do
                pos := Position( obs, obc[i] );
                ims[pos] := ImagesOfObjects(c)[1][i];
            od;
        od;
        return ims;
    fi;
end );

########### this method now appears to be redundant ###########
InstallMethod( ImagesOfObjects, "for a groupoid morphism", true,
    [ IsGroupoidMorphism ], 0,
function( mor )

    local  src, obs, ims, i, c, obc, o, pos;

    src := Source( mor );
    obs := ObjectsOfGroupoid( src );
    ims := ShallowCopy( obs );
    for c in ComponentsOfMorphism( mor ) do
        obc := Source( c )!.objects;
        for i in [1..Length(obc)] do
            pos := Position( obs, obc[i] );
            ims[pos] := ImagesOfObjects( c )[i];
        od;
    od;
    return ims;
end );

#############################################################################
##
#M  IsInjectiveOnObjects                    ## probably needs adjusting ?? ##
#M  IsSurjectiveOnObjects                   ## probably needs adjusting ?? ##
#M  IsBijectiveOnObjects
##
InstallMethod( IsInjectiveOnObjects, "for a groupoid morphism", true,
    [ IsGroupoidMorphism ], 0,
function( mor )

    local  obhoms, imo;

    if HasImagesOfObjects( mor ) then 
        return IsDuplicateFree( Flat( ImagesOfObjects( mor ) ) );
    elif IsMorphismToConnectedGroupoid( mor ) then
        obhoms := ComponentImages( mor );
        imo := Flat( List( obhoms, L -> L[1] ) ); 
        return IsDuplicateFree( imo );
    else  ## morphism has components
        imo := List( ComponentsOfMorphism( mor ), m -> ComponentImages(m)[1] );
        return IsDuplicateFree( Flat( imo ) );
    fi;
end );

InstallMethod( IsSurjectiveOnObjects, "for a groupoid morphism", true,
    [ IsGroupoidMorphism ], 0,
function( mor )
    local  obr, obhoms, imo;
    obr := ObjectsOfGroupoid( Range( mor ) );
    if HasImagesOfObjects( mor ) then 
        imo := Flat( ImagesOfObjects( mor ) );
    elif IsMorphismToConnectedGroupoid( mor ) then
        obhoms := ComponentImages( mor );
        imo := Flat( List( obhoms, L -> L[1] ) );
    else  ## morphism has components
        imo := Flat( List( ComponentsOfMorphism( mor ), 
                     m -> ComponentImages(m)[1][1] ) );
    fi;
    return ( Set(imo) = obr );
end );

InstallMethod( IsBijectiveOnObjects, "for a groupoid morphism", true,
    [ IsGroupoidMorphism ], 0,
function( mor )
    return IsInjectiveOnObjects( mor ) and IsSurjectiveOnObjects( mor );
end );

#############################################################################
##
#M  IsInjective
#M  IsSurjective
##
InstallMethod( IsInjective, "for a groupoid morphism", true,
    [ IsGroupoidMorphism ], 0,
function( mor )
    if HasComponentsOfMorphism( mor ) then
        return ForAll( ComponentsOfMorphism( mor ), c -> IsInjective( c ) );
    else
        return ( IsInjectiveOnObjects( mor ) and 
                 ForAll( ComponentImages( mor ), L -> IsInjective( L[2] ) ) );
    fi;
end );

InstallMethod( IsSurjective, "for a groupoid morphism", true,
    [ IsGroupoidMorphism ], 0,
function( mor )
    if HasComponentsOfMorphism( mor ) then
        return ForAll( ComponentsOfMorphism( mor ), c -> IsSurjective( c ) );
    else
        return ( IsSurjectiveOnObjects( mor ) and
                 ForAll( ComponentImages( mor ), L -> IsSurjective( L[2] ) ) );
    fi;
end );

##############################################################################
##
#M  IsGroupoidEndomorphism( mor ) . . . . . . . . . .  for a groupoid morphism
#M  IsGroupoidAutomorphism( mor ) . . . . . . . . . .  for a groupoid morphism
##
InstallMethod( IsGroupoidEndomorphism, "for a groupoid morphism", true, 
    [ IsGroupoidMorphism ], 0,
function( mor )
    return ( Source( mor ) = Range( mor ) );
end );

InstallMethod( IsGroupoidAutomorphism, "for a groupoid morphism", true, 
    [ IsGroupoidMorphism ], 0,
function( mor )
    return ( IsGroupoidEndomorphism( mor ) and IsInjective( mor ) 
                                           and IsSurjective( mor ) );
end );

#############################################################################
##
#M  InverseGeneralMapping
#M  InverseMorphism
##
InstallMethod( InverseMorphism, "for a groupoid morphism", true,
    [ IsGroupoidMorphism and IsInjective and IsSurjective ], 0,
function( mor )
    return InverseGeneralMapping( mor );
end );

InstallMethod( InverseGeneralMapping, "for a connected morphism", true,
    [ IsMorphismToConnectedGroupoid ], 0,
function( mor )

    local  g1, g2, ob1, ob2, nob, hom21, len, sc1, sc2, obhom1, inv;

    if not ( IsInjectiveOnObjects(mor) and IsSurjectiveOnObjects(mor) ) then
        Error( "morphism not bijective on objects" );
    fi;
    g1 := Source( mor );
    if not IsConnectedGroupoid( g1 ) then
        Error( "source is not connected" );
    fi;
    g2 := Range( mor );
    ob1 := g1!.objects;
    ob2 := g2!.objects;
    nob := Length( ob1 );
    obhom1 := ComponentImages( mor );
    len := Length( obhom1 );
    sc1 := ShallowCopy( ob1 );
    sc2 := ShallowCopy( obhom1[1][1] );
    SortParallel( sc2, sc1 );
    hom21 := InverseGeneralMapping( obhom1[1][2] );
    SetIsTotal( hom21, true );
    SetRespectsMultiplication( hom21, true );
    SetIsInjective( hom21, true );
    SetIsSurjective( hom21, true );
    inv := MorphismOfConnectedGroupoids( g2, g1, sc1, hom21 );
    SetIsInjective( inv, true );
    SetIsSurjective( inv, true );
    return inv;
end );

InstallMethod( InverseGeneralMapping, "for a groupoid morphism", true,
    [ IsGroupoidMorphism ], 0,
function( mor )

    local  comp, isos, inv;

    comp := ComponentsOfMorphism( mor );
    isos := List( comp, m -> InverseGeneralMapping( m ) );
    inv := GroupoidMorphismByComponents( Range(mor), Source(mor), isos );
    SetIsInjective( inv, true );
    SetIsSurjective( inv, true );
    return inv;
end );

#############################################################################
##
#M  \=( <m1>, <m2> )  . . . . . . . . . .  equality of two groupoid morphisms
##
InstallMethod( \=, "for 2 connected morphisms", true,
    [ IsMorphismToConnectedGroupoid, IsMorphismToConnectedGroupoid ], 0,
function( m1, m2 )
    return ( ( Source( m1 ) =  Source( m2 ) ) and
             ( Range( m1 ) = Range( m2 ) ) and 
             ( ComponentImages( m1 ) = ComponentImages( m2 ) ) );
end );

InstallMethod( \=, "for 2 groupoid morphisms", true,
    [ IsGroupoidMorphism, IsGroupoidMorphism ], 0,
function( m1, m2 )

    local  c1, c2, nc, src1, src2, rng1, rng2, j, s, spos, r, rpos, L, ok;

    c1 := ComponentsOfMorphism( m1 );
    c2 := ComponentsOfMorphism( m2 );
    nc := Length( c1 );
    if ( nc <> Length( c2 ) ) then
        return false;
    fi;
    src1 := List( c1, m -> Source( m ) );
    src2 := List( c2, m -> Source( m ) );
    rng1 := List( c1, m -> Range( m ) );
    rng2 := List( c2, m -> Range( m ) );
    L := ListWithIdenticalEntries( nc, 0 );
    for j in [1..nc] do
        s := src1[j];
        spos := Position( src2, s );
        r := rng1[j];
        rpos := Position( rng2, r );
        if ( ( spos = fail ) or ( rpos = fail ) or ( spos <> rpos ) ) then
            return false;
        fi;
        ok := ( c1[j] = c2[spos] );
        if ( ok = false ) then
            return false;
        fi;
        L[j] := spos;
    od;
    Sort( L );
    return ( L = Set(L) );
end );

#############################################################################
##
#M  \*( <m1>, <m2> )  . . . . . . . . . . . product of two groupoid morphisms
#M  \^( <e>, <n> )  . . . . . . . . . . . . . .  power of a groupoid morphism
##
InstallMethod( \*, "for 2 groupoid morphisms", true,
    [ IsGroupoidMorphism, IsGroupoidMorphism ], 0,
function( m1, m2 )
    Print( "general * not yet implemented\n" );
    return fail;
end );

InstallMethod( \*, "for 2 connected groupoid morphisms", true,
    [ IsGroupoidMorphism, IsMorphismToConnectedGroupoid ], 0,
function( mor1, mor2 )

    local  src1, rng1, src2, rng2, pos, comp1, len1, i, m, mors, 
           obhoms, imo1, hom1, s1, ob1, nob1, imo2, hom2, s2, ob2, nob2,
           imo12, j, o, p, hom;

    Info( InfoGpd, 2, "second method for * with mor2 connected" );
    src1 := Source( mor1 );
    rng1 := Range( mor1 );
    src2 := Source( mor2 );
    rng2 := Range( mor2 );
    if not IsSubgroupoid( src2, rng1 ) then
        Error( "Range(mor1) not a subgroupoid of Source(mor2)" );
    fi;
    comp1 := ComponentsOfMorphism( mor1 );
    len1 := Length( comp1 );
    mors := ListWithIdenticalEntries( len1, 0 );
    obhoms := ComponentImages( mor2 );
    for i in [1..len1] do
        m := comp1[i];
        if not IsConnectedGroupoid( Source(m) ) then
            Print( "general * not yet implemented\n" );
            return fail;
        fi;
        imo1 := ComponentImages(m)[1][1];
        hom1 := ComponentImages(m)[1][2];
        s1 := Source( m );
        ob1 := s1!.objects;
        nob1 := Length( ob1 );
        pos := Position( ComponentsOfGroupoid( src2 ), Range( m ) );
        s2 := ComponentsOfGroupoid( src2 )[pos];
        ob2 := s2!.objects;
        nob2 := Length( ob2 );
        imo2 := obhoms[pos][1];
        hom2 := obhoms[pos][2];
        imo12 := ListWithIdenticalEntries( nob1, 0 );
        for j in [1..nob1] do
            o := imo1[j];
            p := Position( ob2, o );
            imo12[j] := imo2[p];
        od;
        hom := hom1 * hom2;
        mors[i] := MorphismToConnectedGroupoid( s1, rng2, [ [imo12, hom] ] );
    od;
    return GroupoidMorphismByComponents( src1, rng2, mors );
end );

InstallMethod( \*, "for 2 connected groupoid morphisms", true,
    [ IsMorphismToConnectedGroupoid, IsMorphismToConnectedGroupoid ], 0,
function( m1, m2 )

    local  s1, r1, s2, r2, oi1, ob1, nob1, ob2, oi2, oi12, j, o, p, hom;

    s1 := Source( m1 );
    if not IsConnectedGroupoid( s1 ) then 
        Error( "source of m1 not connected - general method not installed" );
    fi;
    r1 := Range( m1 );
    s2 := Source( m2 );
    r2 := Range( m2 );
    if not IsSubgroupoid( s2, r1 ) then
        Error( "Range(m1) not a subgroupoid of Source(m2)" );
    fi;
    oi1 := ComponentImages( m1 )[1];
    ob1 := s1!.objects;
    nob1 := Length( ob1 );
    ob2 := s2!.objects;
    oi2 := ComponentImages( m2 )[1];
    oi12 := ListWithIdenticalEntries( nob1, 0 );
    for j in [1..nob1] do
        o := oi1[1][j];
        p := Position( ob2, o );
        oi12[j] := oi2[1][p];
    od;
    hom := oi1[2] * oi2[2];
    return MorphismToConnectedGroupoid( s1, r2, [ [oi12, hom] ] );
end );

InstallMethod( \^, "for a groupoid morphism and an integer", true,
    [ IsGroupoidMorphism, IsInt ], 0,
function( m, n )
    Print( "* only implemented for connected morphisms at present\n" );
    return fail;
end );

InstallMethod( \^, "for a connected groupoid morphism and an integer", true,
    [ IsMorphismToConnectedGroupoid, IsInt ], 0,
function( mor, n )
    local  i, m1, m2;

    if not ( Source( mor ) = Range( mor ) ) then
        Error( "source <> range" );
    fi;
    if ( n = 1 ) then
        return mor;
    elif ( n = 0 ) then
        return IdentityMapping( Source( mor ) );
    elif ( n = -1 ) then
        return InverseGeneralMapping( mor );
    elif ( n > 1 ) then
        m1 := mor;
        for i in [2..n] do
            m2 := mor * m1;
            m1 := m2;
        od;
        return m1;
    else  ## n < -1
        return InverseGeneralMapping( mor )^(-n);
    fi;
    Info( InfoGpd, 1, "unexpected failure in \^" ); 
    return fail;
end );

#############################################################################
##
#M  Order( <m> )  . . . . . . . . . . . . . . . . . .  of a groupoid morphism
##
InstallMethod( Order, "for a groupoid morphism", true,
    [ IsGroupoidMorphism ], 0,
function( mor )
    Print( "* only implemented for connected morphisms at present\n" );
    return fail;
end );

InstallMethod( Order, "for a connected groupoid morphism", true,
    [ IsMorphismToConnectedGroupoid ], 0,
function( m )
    local  im, obsrc, oblist, obord, hom, homord, ok;

    if not ( Source( m ) = Range( m ) ) then
        Error( "source <> range" );
    fi;
    im := InverseMorphism( m ); 
    if (  im = fail ) then
        Error( "inverse does not exist" );
    fi;
    obsrc := ObjectsOfGroupoid( Source( m ) );
    oblist := List( ImagesOfObjects( m ), o -> Position( obsrc, o ) );
    obord := Order( PermList( oblist ) );
    hom := ComponentImages(m)[1][2];
    homord := Order( hom);
    return Lcm( [ obord, homord ] );
end );

#############################################################################
##
#M  PrintObj
##
InstallMethod( PrintObj, "for a groupoid morphism", true,
    [ IsGroupoidMorphism ], 0,
function ( mor )
    Print( "Groupoid morphism : ", Source(mor), " -> ", Range(mor) ); 
end );

#############################################################################
##
#M  Display
##
InstallMethod( Display, "for a morphism to a connected groupoid", true,
    [ IsMorphismToConnectedGroupoid ], 0,
function ( mor )

    local  homs, imo, src, rng, comp, i, c, obhoms, len;

    Print( "Morphism to connected groupoid" );
    src := Source( mor );
    rng := Range( mor );
    obhoms := ComponentImages( mor );
    len := Length( obhoms );
    if ( len = 1 ) then
        Print( ":\n" );
        Print( "[ ", src, " ] -> [ ", rng, " ]\n" );
        Print( "  object map = ", [ src!.objects, obhoms[1][1] ], "\n" );
        Print( "homomorphism = ", MappingGeneratorsImages(obhoms[1][2]),"\n");
    else
        Print( " with components:\n" );
        comp := ComponentsOfGroupoid( src );
        for i in [1..Length(comp)] do
            c := comp[i];
            Print( "[ ", c, " ] -> [ ", rng, " ]\n" );
            Print( "  object map = ", [ c!.objects, obhoms[i][1] ], "\n" );
            Print( "homomorphism = ", 
                   MappingGeneratorsImages( obhoms[i][2] ), "\n" );
        od;
    fi;
end );

InstallMethod( Display, "generic method for a groupoid morphism", true,
    [ IsGroupoidMorphism ], 0,
function ( mor )

    local  cmor, src, rng, comp, i, c, obhoms, len;

    Print( "Groupoid Morphism: ", Source( mor ), " -> ", 
            Range( mor ), " with components :\n" );
    for cmor in ComponentsOfMorphism( mor ) do
        src := Source( cmor );
        rng := Range( cmor );
        obhoms := ComponentImages( cmor );
        len := Length( obhoms );
        if ( len = 1 ) then
            Print( "[ ", src, " ] -> [ ", rng, " ]\n" );
            Print( "  object map = ", [ src!.objects, obhoms[1][1] ], "\n" );
            Print( "homomorphism = ", 
                   MappingGeneratorsImages( obhoms[1][2] ), "\n" );
        else 
            comp := ComponentsOfGroupoid( src );
            for i in [1..Length(comp)] do
                c := comp[i];
                Print( "[ ", c, " ] -> [ ", rng, " ]\n" );
                Print( "  object map = ", [ c!.objects, obhoms[i][1] ], "\n" );
                Print( "homomorphism = ", 
                       MappingGeneratorsImages( obhoms[i][2] ), "\n" );
            od;
        fi;
    od;
end );

#############################################################################
##
#M  ViewObj
##
InstallMethod( ViewObj, "for a groupoid morphism", true,
    [ IsGroupoidMorphism ], 0,
function ( mor )
    Print( "Groupoid morphism : ", Source(mor), " -> ", Range(mor) ); 
end );

#############################################################################
##
#M  ImageElm( <mor>, <e> )
##
InstallOtherMethod( ImageElm, "for a groupoid morphism", true,
    [ IsMorphismToConnectedGroupoid, IsGroupoidElementRep ], 0,
function ( mor, e )

    local  G1, ce, C1, pe, Ge, obs1, hom, obs2, t2, h2, g2;

    ##  need to include some tests here ??
    G1 := Source( mor );
    ce := ComponentOfObject( G1, e![2] );
    C1 := ComponentsOfGroupoid( G1 );
    pe := Position( C1, ce );
    Ge := C1[pe];
    obs1 := Ge!.objects;
    hom := ComponentImages( mor )[pe][2];
    obs2 := ComponentImages( mor )[pe][1];
    t2 := obs2[ Position( obs1, e![2] ) ];
    h2 := obs2[ Position( obs1, e![3] ) ];
    g2 := Image( hom, e![1] );
    return GroupoidElementNC( g2, t2, h2 );
end );

InstallOtherMethod( ImageElm, "for a groupoid morphism", true,
    [ IsGroupoidMorphism, IsGroupoidElementRep ], 0,
function ( mor, e )
    Print( "not yet implemented ImageElm for a general morphism\n" );
    return fail;
end );

InstallOtherMethod( ImagesSource, "for a groupoid morphism", true,
    [ IsMorphismToConnectedGroupoid ], 0,
function ( mor )
    local  imo, hom, img, rng;

    if not IsInjectiveOnObjects( mor ) then
        Error( "not yet implemented when not injective on objects" );
    fi;
    imo := ShallowCopy( ImagesOfObjects( mor ) );
    Sort( imo );
    hom := ComponentImages( mor )[1][2];
    img := Image( hom );
    rng := Range( mor );
    if ( ( imo = rng!.objects ) and ( img = rng!.group ) ) then
        return rng;
    else
        return Subgroupoid( rng, [ [ imo, img ] ] );
    fi;
end );

InstallOtherMethod( ImagesSource, "for a groupoid morphism", true,
    [ IsGroupoidMorphism ], 0,
function ( mor )
    Error( "not yet implemented when Range(mor) is not connected" );
end );

##############################################################################
##
#E  gpdmor.gi  . . . . . . . . . . . . . . . . . . . . . . . . . . . ends here
##
