##
##  Datastructures: GAP package providing common datastructures.
##
##  Copyright (C) 2015-2017  The datastructures team.
##  For list of the team members, please refer to the COPYRIGHT file.
##
##  This package is licensed under the GPL 2 or later, please refer
##  to the COPYRIGHT.md and LICENSE files for details.
##

##
##  Implementation of a hash set for GAP.
##

#! @Chapter Hashsets
#! @Section API
InstallGlobalFunction(HashSet,
function(arg...)
    local hashfunc, eqfunc, capacity, res;

    hashfunc := HashBasic;
    eqfunc := \=;
    capacity := 16;

    if Length(arg) > 0 and IsFunction(arg[1]) then
        hashfunc := Remove(arg, 1);
    fi;
    if Length(arg) > 0 and IsInt(arg[Length(arg)]) then
        capacity := Remove(arg);
    fi;
    if Length(arg) = 1 and IsFunction(arg[1]) then
        eqfunc := Remove(arg);
    fi;
    if Length(arg) > 0 then
        Error("Invalid arguments");
    fi;

    res := DS_Hash_Create(hashfunc, eqfunc, capacity, true);
    return res;
end);

InstallMethod(ViewObj, "for hashsets",
    [ IsHashSetRep ],
function(ht)
    Print("<hash set obj capacity=",DS_Hash_Capacity(ht),
            " used=",DS_Hash_Used(ht),">");
end);

#! @Description
#! Add <A>obj</A> to <A>hashset</A>.
#! @Arguments hashset, obj
InstallOtherMethod(AddSet,
    "for a hash set and a key",
    [ IsHashSetRep, IsObject ],
    DS_Hash_AddSet);

#! @Description
#! Test membership of <A>obj</A> in <A>hashset</A>
#! @Arguments obj, hashset
InstallOtherMethod( \in,
    "for a hash set and a key",
    [ IsObject, IsHashSetRep ],
    {key, ht} -> DS_Hash_Contains(ht, key));

#! @Description
#! Remove <A>obj</A> from <A>hashset</A>.
#! @Arguments hashset, obj
InstallOtherMethod( RemoveSet,
    "for a hash set and a key",
    [ IsHashSetRep, IsObject ],
    DS_Hash_Delete);

#! @Description
#! Return the size of a hashset
#! @Arguments hashset
#! Returns an integer
InstallOtherMethod( Size,
    "for a hash set",
    [ IsHashSetRep ],
    ht -> DS_Hash_Used(ht));

#! @Description
#! Test a hashset for emptiness.
#! @Arguments hashset
#! @Returns a boolean
InstallOtherMethod( IsEmpty,
    "for a hash set",
    [ IsHashSetRep ],
    ht -> DS_Hash_Used(ht) = 0);

#! @Description
#! Convert a hashset into a &GAP; set
#! @Arguments hashset
#! @Returns a set
InstallMethod( Set,
    "for a hash set",
    [ IsHashSetRep ],
    ht -> Difference(Set(ht![5]),[fail]));

#! @Description
#! Convert a hashset into a &GAP; set
#! @Arguments hashset
#! @Returns an immutable set
InstallMethod( AsSet,
    "for a hash set",
    [ IsHashSetRep ],
    ht -> MakeImmutable(Set(ht)));


BindGlobal( "NextIterator_HashSet", function(iter)
    local val, idx;
    if iter!.next > Length(iter!.list) then
        Error("<iter> is exhausted");
    fi;
    val := iter!.list[ iter!.next ];
    idx := iter!.next + 1;
    # skip to the next bound entry not equal to 'fail' (which marks deleted entries)
    while idx <= Length(iter!.list) and not
        (IsBound(iter!.list[idx]) and iter!.list[idx] <> fail) do
        idx := idx + 1;
    od;
    iter!.next := idx;
    return val;
end);

#! @Description
#! Create an iterator for the values contained in a hashset.
#! Note that elements added to the hashset after
#! the creation of an iterator are not guaranteed to be returned by that iterator.
#! @Arguments set
#! @Returns an iterator
InstallMethod( Iterator,
    "for a hash set",
    [ IsHashSetRep ],
function(ht)
    local iter;
    iter := rec(list := ht![5], next := PositionProperty(~.list, x -> x <> fail));
    iter.ShallowCopy := iter -> rec(list := iter!.list, next := iter!.next);
    iter.IsDoneIterator := iter -> iter!.next > Length(iter!.list);
    iter.NextIterator   := NextIterator_HashSet;

    return IteratorByFunctions( iter );
end);


# TODO: things we could implement (but do we want to?)
# UnitSet
# Union
# Intersection
# ...
#
# But do we really want to???
