"""
    AbstractSymmetry

Supertype for symmetries in a spin system.

# External Interface
- [`apply(symmetry, index)`](@ref)

# Internal Interface
Subtypes must implement:
- [`_order(::MyType)`](@ref)
- `(::MyType)(x)` should apply the symmetry operation to a BINARY index `x` in range 0:2^N-1
"""
abstract type AbstractSymmetry end

"""
    apply(symmetry, index)

Compute the action of the symmetry operation on a basis state given by index.
"""
function apply end

# Default: convert between 1-based indexing and bit representation of spin bases
apply(symmetry::AbstractSymmetry, index) = symmetry(index-1)+1


"""
    _order(symmetry)

Return the size of the group generated by the symmetry; i.e. the smallest, positive number k
such that `symmetry` applied k times is the identity function.
"""
function _order end



# Act as a scalar for broadcasting.
# Since a symmetry behaves like a function object, this enables standard broadcasting.
Base.Broadcast.broadcastable(as::AbstractSymmetry) = Ref(as)

Base.:(==)(symm1::T, symm2::T) where T<:AbstractSymmetry = hash(symm1) == hash(symm2)

const hashs_seed_symmetry = UInt === UInt64 ? 0xcd254b02e510da2d : 0x2d61926b
## Note that this hash function is independent on the order of fields
## This means that Swap(1,2) and Swap(2,1) are considered identically - which is correct I think
function Base.hash(symm::AbstractSymmetry, h::UInt)
    hv = hashs_seed_symmetry
    fields = fieldnames(typeof(symm))
    for f in fields
        hv ⊻= hash(getfield(symm, f))
    end
    return hash(hv, h)
end
