package com.atlassian.bitbucket.permission;

import com.atlassian.bitbucket.user.ApplicationUser;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Represents a permission request, which can be used to request users with a specific permission, or verify that
 * users have a specific permission.
 * <p>
 * Note that this class does not validate the relationship between {@link #getPermission() the permission} and
 * {@link #getResource() the resource} that it holds. The APIs accepting instances of permission requests are
 * responsible for verifying whether given permission request is valid.
 */
public class PermissionRequest {

    private final Permission permission;
    private final Object resource;
    private final ApplicationUser user;

    protected PermissionRequest(Builder builder) {
        this.permission = builder.permission;
        this.resource = builder.resource;
        this.user = builder.user;
    }

    /**
     * @return permission to satisfy the request, which can be either a {@link Permission#isGlobal() global permission},
     * or a resource permission to the specified {@link #getResource() resource}
     */
    @Nonnull
    public Permission getPermission() {
        return permission;
    }

    /**
     * Resource to which the {@link #getPermission() permission} should apply. This could be a domain object (e.g.
     * project or repository), as well as an ID.
     *
     * @return resource to which the {@link #getPermission() permission} should apply, or {@code null} if this request
     *         does not concern any particular resource (which should be the case for
     *         {@link Permission#isGlobal() global permissions})
     */
    @Nullable
    public Object getResource() {
        return resource;
    }

    /**
     * Resource to which the {@link #getPermission() permission} should apply, as a client-specified instance of
     * {@code resourceType}.
     *
     * @return resource to which the {@link #getPermission() permission} should apply, or {@code null} if this request
     *         does not concern any particular resource (which should be the case for
     *         {@link Permission#isGlobal() global permissions})
     * @throws ClassCastException if the resource is not an instance of the expected {@code resourceType}
     */
    @Nullable
    public <T> T getResourceAs(@Nonnull Class<T> resourceType) {
        return checkNotNull(resourceType, "resourceType").cast(resource);
    }

    /**
     * @return the user that should have the {@link #getPermission() permission}, or {@code null} if this request does
     * not concern any particular user
     */
    @Nullable
    public ApplicationUser getUser() {
        return user;
    }

    public static class Builder {

        private final Permission permission;

        private Object resource;
        private ApplicationUser user;

        public Builder(@Nonnull Permission permission) {
            this.permission = checkNotNull(permission, "permission");
        }

        public Builder(@Nonnull PermissionRequest request) {
            this.permission = request.permission;
            this.resource = request.resource;
            this.user = request.user;
        }

        @Nonnull
        public PermissionRequest build() {
            return new PermissionRequest(this);
        }

        @Nonnull
        public Builder resource(@Nullable Object value) {
            resource = value;

            return this;
        }

        @Nonnull
        public Builder user(@Nullable ApplicationUser value) {
            user = value;

            return this;
        }
    }
}
