package com.atlassian.bitbucket.mesh.repair;

import com.atlassian.bitbucket.mesh.rpc.v1.RpcRepairCapabilitiesAdvertisement;

import javax.annotation.Nonnull;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

import static java.util.Objects.requireNonNull;
import static java.util.Optional.empty;
import static java.util.Optional.of;
import static java.util.stream.Collectors.toList;

/**
 * Enum of Mesh capabilities, used to determine common capabilities in Mesh-Mesh communication with mixed Mesh
 * versions.
 */
public enum RepairCapability {; //Remove ; when adding the first capability. It's here to satisfy our checkstyle overlords

    //It's intentional that there is no mapping for RpcCapability.CAPABILITY_UNKNOWN;
    //UNKNOWN is not a 'real' capability

    /**
     * Advertisement of all supported capabilities
     */
    public static final RpcRepairCapabilitiesAdvertisement ADVERTISEMENT =
            RpcRepairCapabilitiesAdvertisement.newBuilder()
                    .addAllCapabilities(Arrays.stream(values())
                            .map(RepairCapability::getId)
                            .collect(toList()))
                    .build();

    private final String id;

    RepairCapability(String id) {
        this.id = id;
    }

    /**
     * Translates the provided {@code id} into a {@link RepairCapability}, if recognized.
     *
     * @param id the capability ID
     * @return the mapped capability, or {@link Optional#empty()} if not recognized
     */
    @Nonnull
    public static Optional<RepairCapability> fromId(@Nonnull String id) {
        requireNonNull(id, "id");

        for (RepairCapability capability : values()) {
            if (capability.id.equals(id)) {
                return of(capability);
            }
        }
        return empty();
    }

    /**
     * Calculates the shared capabilities from this version's capabilities and the provided {@code advertised}
     * advertised capabilities.
     *
     * @param advertisement the capabilities advertised by another Mesh node
     * @return a set with the shared capabilities
     */
    @Nonnull
    public static Set<RepairCapability> getSharedCapabilities(
            @Nonnull RpcRepairCapabilitiesAdvertisement advertisement) {
        requireNonNull(advertisement, "advertisement");

        //Any capability advertised by the other node that we recognize is shared
        Set<RepairCapability> sharedCapabilities = EnumSet.noneOf(RepairCapability.class);
        advertisement.getCapabilitiesList().stream()
                .flatMap(capability -> fromId(capability).map(Stream::of).orElseGet(Stream::empty))
                .forEach(sharedCapabilities::add);
        return sharedCapabilities;
    }

    @Nonnull
    public String getId() {
        return id;
    }
}
