package me.izstas.rfs.client.rfs;

import java.io.IOException;
import java.net.URI;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.URIUtils;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

import me.izstas.rfs.model.Metadata;
import me.izstas.rfs.model.Version;

/**
 * A helper class for executing RFS API calls.
 */
public final class Rfs {
    private static final ObjectMapper mapper = new ObjectMapper();

    private final ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
    private final CloseableHttpClient client;
    private final HttpClientContext context;
    private final URI uri;

    /**
     * Constructs this class for executing RFS API calls, authenticating anonymously.
     * @param uri RFS API base URI
     */
    public Rfs(URI uri) {
        this.uri = uri;

        client = HttpClients.createDefault();
        context = HttpClientContext.create();
    }

    /**
     * Constructs this class for executing RFS API calls, authenticating with specified username and password.
     * @param uri RFS API base URI
     * @param username the username for authentication
     * @param password the password for authentication
     */
    public Rfs(URI uri, String username, String password) {
        this.uri = uri;

        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(new AuthScope(URIUtils.extractHost(uri)), new UsernamePasswordCredentials(username, password));

        client = HttpClients.custom()
                .setDefaultCredentialsProvider(credentialsProvider)
                .build();

        AuthCache authCache = new BasicAuthCache();
        authCache.put(URIUtils.extractHost(uri), new BasicScheme());

        context = HttpClientContext.create();
        context.setAuthCache(authCache);
    }

    /**
     * Releases all resources allocated by this object.
     */
    public void shutdown() {
        try {
            client.close();
        }
        catch (IOException e) {
            throw new AssertionError(e); // Should never happen, HttpClient catches IOException inside
        }

        executor.shutdownNow();
    }


    /**
     * Performs GET / (get version information) API call.
     */
    public ListenableFuture<Version> getVersion() {
        final HttpGet request = new HttpGet(uri);
        request.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.getMimeType());

        return executor.submit(new Callable<Version>() {
            @Override
            public Version call() throws Exception {
                try (CloseableHttpResponse response = client.execute(request, context)) {
                    return checkAndParseResponse(response, Version.class);
                }
            }
        });
    }

    /**
     * Performs GET /metadata/@path (get file metadata) API call.
     */
    public ListenableFuture<Metadata> getMetadata(String path) {
        final HttpGet request = new HttpGet(uri.resolve(path));
        request.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.getMimeType());

        return executor.submit(new Callable<Metadata>() {
            @Override
            public Metadata call() throws Exception {
                try (CloseableHttpResponse response = client.execute(request, context)) {
                    return checkAndParseResponse(response, Metadata.class);
                }
            }
        });
    }


    private <T> T checkAndParseResponse(HttpResponse response, Class<T> clazz) throws IOException {
        if (response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
            throw new RfsAuthenticationException();
        }
        if (response.getStatusLine().getStatusCode() == HttpStatus.SC_FORBIDDEN) {
            throw new RfsAccessException();
        }

        try {
            return mapper.readValue(response.getEntity().getContent(), clazz);
        }
        catch (JsonProcessingException e) {
            throw new RfsResponseException(e);
        }
    }
}

@@ -4036,24 +4036,109 @@
 ing path) %7B%0A
+        if (path.startsWith(%22/%22)) %7B%0A            path = path.substring(1);%0A        %7D%0A%0A
         fina
@@ -4181,16 +4181,37 @@
 resolve(
+%22metadata/%22).resolve(
 path));%0A

Fix request path in getMetadata method