package com.mojang.realmsclient.client;

import org.apache.http.util.Args;
import java.io.OutputStream;
import org.apache.http.entity.InputStreamEntity;
import org.apache.logging.log4j.LogManager;
import org.apache.http.Header;
import java.time.Duration;
import java.util.function.Function;
import com.google.gson.JsonElement;
import java.util.Optional;
import com.google.gson.JsonParser;
import org.apache.http.util.EntityUtils;
import java.io.FileNotFoundException;
import org.apache.http.HttpEntity;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.IOException;
import org.apache.http.HttpResponse;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.client.methods.HttpPost;
import java.util.function.Consumer;
import java.util.concurrent.TimeUnit;
import org.apache.http.client.config.RequestConfig;
import com.mojang.realmsclient.gui.screens.UploadResult;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import com.mojang.realmsclient.dto.UploadInfo;
import java.io.File;
import org.apache.logging.log4j.Logger;

public class FileUpload {
    private static final Logger LOGGER;
    private final File file;
    private final long worldId;
    private final int slotId;
    private final UploadInfo uploadInfo;
    private final String sessionId;
    private final String username;
    private final String clientVersion;
    private final UploadStatus uploadStatus;
    private final AtomicBoolean cancelled;
    private CompletableFuture<UploadResult> uploadTask;
    private final RequestConfig requestConfig;
    
    public FileUpload(final File file, final long long2, final int integer, final UploadInfo uploadInfo, final String string5, final String string6, final String string7, final UploadStatus czc) {
        this.cancelled = new AtomicBoolean(false);
        this.requestConfig = RequestConfig.custom().setSocketTimeout((int)TimeUnit.MINUTES.toMillis(10L)).setConnectTimeout((int)TimeUnit.SECONDS.toMillis(15L)).build();
        this.file = file;
        this.worldId = long2;
        this.slotId = integer;
        this.uploadInfo = uploadInfo;
        this.sessionId = string5;
        this.username = string6;
        this.clientVersion = string7;
        this.uploadStatus = czc;
    }
    
    public void upload(final Consumer<UploadResult> consumer) {
        if (this.uploadTask != null) {
            return;
        }
        (this.uploadTask = CompletableFuture.<UploadResult>supplyAsync(() -> this.requestUpload(0))).thenAccept(consumer);
    }
    
    public void cancel() {
        this.cancelled.set(true);
        if (this.uploadTask != null) {
            this.uploadTask.cancel(false);
            this.uploadTask = null;
        }
    }
    
    private UploadResult requestUpload(final int integer) {
        final UploadResult.Builder a3 = new UploadResult.Builder();
        if (this.cancelled.get()) {
            return a3.build();
        }
        this.uploadStatus.totalBytes = this.file.length();
        final HttpPost httpPost4 = new HttpPost("http://" + this.uploadInfo.getUploadEndpoint() + ":" + this.uploadInfo.getPort() + "/upload" + "/" + this.worldId + "/" + this.slotId);
        final CloseableHttpClient closeableHttpClient5 = HttpClientBuilder.create().setDefaultRequestConfig(this.requestConfig).build();
        try {
            this.setupRequest(httpPost4);
            final HttpResponse httpResponse6 = (HttpResponse)closeableHttpClient5.execute((HttpUriRequest)httpPost4);
            final long long7 = this.getRetryDelaySeconds(httpResponse6);
            if (this.shouldRetry(long7, integer)) {
                return this.retryUploadAfter(long7, integer);
            }
            this.handleResponse(httpResponse6, a3);
        }
        catch (Exception exception6) {
            if (!this.cancelled.get()) {
                FileUpload.LOGGER.error("Caught exception while uploading: ", (Throwable)exception6);
            }
        }
        finally {
            this.cleanup(httpPost4, closeableHttpClient5);
        }
        return a3.build();
    }
    
    private void cleanup(final HttpPost httpPost, final CloseableHttpClient closeableHttpClient) {
        httpPost.releaseConnection();
        if (closeableHttpClient != null) {
            try {
                closeableHttpClient.close();
            }
            catch (IOException iOException4) {
                FileUpload.LOGGER.error("Failed to close Realms upload client");
            }
        }
    }
    
    private void setupRequest(final HttpPost httpPost) throws FileNotFoundException {
        httpPost.setHeader("Cookie", "sid=" + this.sessionId + ";token=" + this.uploadInfo.getToken() + ";user=" + this.username + ";version=" + this.clientVersion);
        final CustomInputStreamEntity a3 = new CustomInputStreamEntity(new FileInputStream(this.file), this.file.length(), this.uploadStatus);
        a3.setContentType("application/octet-stream");
        httpPost.setEntity((HttpEntity)a3);
    }
    
    private void handleResponse(final HttpResponse httpResponse, final UploadResult.Builder a) throws IOException {
        final int integer4 = httpResponse.getStatusLine().getStatusCode();
        if (integer4 == 401) {
            FileUpload.LOGGER.debug("Realms server returned 401: " + httpResponse.getFirstHeader("WWW-Authenticate"));
        }
        a.withStatusCode(integer4);
        if (httpResponse.getEntity() != null) {
            final String string5 = EntityUtils.toString(httpResponse.getEntity(), "UTF-8");
            if (string5 != null) {
                try {
                    final JsonParser jsonParser6 = new JsonParser();
                    final JsonElement jsonElement7 = jsonParser6.parse(string5).getAsJsonObject().get("errorMsg");
                    final Optional<String> optional8 = Optional.<JsonElement>ofNullable(jsonElement7).<String>map(JsonElement::getAsString);
                    a.withErrorMessage(optional8.orElse(null));
                }
                catch (Exception ex) {}
            }
        }
    }
    
    private boolean shouldRetry(final long long1, final int integer) {
        return long1 > 0L && integer + 1 < 5;
    }
    
    private UploadResult retryUploadAfter(final long long1, final int integer) throws InterruptedException {
        Thread.sleep(Duration.ofSeconds(long1).toMillis());
        return this.requestUpload(integer + 1);
    }
    
    private long getRetryDelaySeconds(final HttpResponse httpResponse) {
        return Optional.<Header>ofNullable(httpResponse.getFirstHeader("Retry-After")).map(Header::getValue).<Long>map(Long::valueOf).orElse(0L);
    }
    
    public boolean isFinished() {
        return this.uploadTask.isDone() || this.uploadTask.isCancelled();
    }
    
    static {
        LOGGER = LogManager.getLogger();
    }
    
    static class CustomInputStreamEntity extends InputStreamEntity {
        private final long length;
        private final InputStream content;
        private final UploadStatus uploadStatus;
        
        public CustomInputStreamEntity(final InputStream inputStream, final long long2, final UploadStatus czc) {
            super(inputStream);
            this.content = inputStream;
            this.length = long2;
            this.uploadStatus = czc;
        }
        
        public void writeTo(final OutputStream outputStream) throws IOException {
            Args.notNull(outputStream, "Output stream");
            final InputStream inputStream3 = this.content;
            try {
                final byte[] arr4 = new byte[4096];
                if (this.length < 0L) {
                    int integer5;
                    while ((integer5 = inputStream3.read(arr4)) != -1) {
                        outputStream.write(arr4, 0, integer5);
                        final UploadStatus uploadStatus = this.uploadStatus;
                        uploadStatus.bytesWritten += (Long)integer5;
                    }
                }
                else {
                    long long6 = this.length;
                    while (long6 > 0L) {
                        final int integer5 = inputStream3.read(arr4, 0, (int)Math.min(4096L, long6));
                        if (integer5 == -1) {
                            break;
                        }
                        outputStream.write(arr4, 0, integer5);
                        final UploadStatus uploadStatus2 = this.uploadStatus;
                        uploadStatus2.bytesWritten += (Long)integer5;
                        long6 -= integer5;
                        outputStream.flush();
                    }
                }
            }
            finally {
                inputStream3.close();
            }
        }
    }
}
