package com.atlassian.stash.internal.avatar;

import com.atlassian.bitbucket.avatar.AvatarSupplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.MediaType;

import javax.annotation.Nonnull;
import java.util.List;
import java.util.Set;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.lang.Math.abs;

/**
 * @since 2.1
 */
public enum AvatarType {

    PROJECT(1, "projects", MediaType.SVG_UTF_8) {

        /**
         * Lists the available colours for default project avatars.
         */
        private final List<String> colours = ImmutableList.of("blue", "green", "purple", "red", "yellow");

        /**
         * Uses the hash code of the provided {@code id} to select a pseudo-random colour and returns a default project
         * avatar of the requested size in that colour.
         *
         * @param id   the ID of the project for which the default avatar is being requested
         * @param size the size of the avatar being requested
         * @return a {@link AvatarSupplier supplier} which will produce a pseudo-random default project avatar
         * @throws IllegalArgumentException if the requested {@code size} is not available
         * @throws NullPointerException if the provided {@code id} is {@code null}
         */
        @Nonnull
        @Override
        protected String buildPath(@Nonnull String id, int size) {
            checkNotNull(id, "id");
            checkArgument(DEFAULT_SIZES.contains(size), "Default project avatars are not available in %s px", size);

            //Use the hash code of the ID to pick a colour. This way the colours should vary pseudo-randomly, but
            //should still be stable across requests so they don't strobe
            int hash = id.hashCode();                   //Note that the resulting hash code may be negative
            int selection = abs(hash % colours.size()); //So we ensure the selection ID is never negative
            String colour = colours.get(selection);

            return "avatars/project/" + colour + "/" + size + ".svg";
        }
    },
    /**
     * @since 2.12
     */
    USER(2, "users", MediaType.PNG) {

        /**
         * Retrieves a silhouette default image in the requested size.
         *
         * @param id   ignored
         * @param size the size of the avatar being requested
         * @return a {@link AvatarSupplier supplier} which will produce a silhouette default image
         * @throws IllegalArgumentException if the requested {@code size} is not available
         * @throws NullPointerException if the provided {@code id} is {@code null}
         */
        @Nonnull
        @Override
        protected String buildPath(@Nonnull String id, int size) {
            checkNotNull(id, "id");
            checkArgument(DEFAULT_SIZES.contains(size), "Default user avatars are not available in %s px", size);

            return "avatars/user/" + size + ".png";
        }
    };

    /**
     * Defines the sizes in which default avatars are available.
     * <p>
     * Note: It is assumed sizes have been normalised prior to attempting to retrieve a default avatar. This set
     * exists purely to try and catch cases where developer error might allow through a request for a size which
     * does not exist.
     */
    static final Set<Integer> DEFAULT_SIZES = ImmutableSet.of(48, 64, 96, 128, 256);

    private final String contentType;
    private final String directoryName;
    private final int id;

    AvatarType(int id, String directoryName, MediaType mediaType) {
        this.directoryName = checkNotNull(directoryName, "directoryName");
        this.id = id;

        contentType = mediaType.toString();
    }

    @SuppressWarnings("unused")
    public static AvatarType fromId(int id) {
        for (AvatarType value : values()) {
            if (value.getId() == id) {
                return value;
            }
        }
        throw new IllegalArgumentException("No AvatarType is available for ID " + id);
    }

    /**
     * @return the content type for avatars of this type
     * @since 4.4
     */
    @Nonnull
    public String getContentType() {
        return contentType;
    }

    /**
     * @return the directory on disk where avatars of this type are stored
     */
    @Nonnull
    public String getDirectoryName() {
        return directoryName;
    }

    /**
     * @return a fixed ID for this avatar type, used for serialization
     */
    public int getId() {
        return id;
    }

    /**
     * Retrieves a default avatar, potentially randomized based on the requested ID.
     *
     * @param id   the entity ID for which a default avatar is being requested
     * @param size the size of the avatar being requested
     * @return a supplier for providing access to the selected default avatar
     */
    @Nonnull
    public AvatarSupplier loadDefault(@Nonnull String id, int size) {
        return new ResourceAvatarSupplier(contentType, buildPath(id, size));
    }

    /**
     * Retrieves a fixed default avatar, agnostic of any form of identifier. This is primarily intended to simplify
     * retrieving a default avatar in anonymous or unauthorized contexts where a fixed avatar should be returned to
     * prevent leaking information about whether or not a given object exists.
     *
     * @param size the size of the avatar being requested
     * @return a supplier providing access to the default avatar
     * @since 2.12
     */
    @Nonnull
    public AvatarSupplier loadFixedDefault(int size) {
        return loadDefault("unknown", size);
    }

    /**
     * Builds the resource path used to stream the requested default avatar.
     *
     * @param id   the entity ID, potentially used to randomize the default avatar
     * @param size the size of the avatar being requested
     * @return the resource path to open to stream the requested default avatar
     * @since 4.4
     */
    @Nonnull
    protected abstract String buildPath(@Nonnull String id, int size);
}
