package com.mojang.realmsclient.gui.screens;

import org.apache.logging.log4j.LogManager;
import java.io.InputStream;
import org.apache.commons.compress.utils.IOUtils;
import java.io.FileInputStream;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import java.io.OutputStream;
import java.util.zip.GZIPOutputStream;
import java.io.FileOutputStream;
import com.mojang.realmsclient.dto.UploadInfo;
import com.mojang.realmsclient.exception.RealmsServiceException;
import java.io.IOException;
import com.mojang.realmsclient.client.FileUpload;
import java.io.File;
import com.mojang.realmsclient.exception.RetryCallException;
import com.mojang.realmsclient.util.UploadTokenCache;
import java.util.concurrent.TimeUnit;
import com.mojang.realmsclient.client.RealmsClient;
import java.util.List;
import com.google.common.collect.Lists;
import net.minecraft.realms.RealmsDefaultVertexFormat;
import net.minecraft.realms.Tezzelator;
import com.mojang.blaze3d.systems.RenderSystem;
import java.util.Locale;
import net.minecraft.realms.Realms;
import net.minecraft.realms.AbstractRealmsButton;
import java.util.concurrent.locks.ReentrantLock;
import net.minecraft.realms.RealmsButton;
import com.google.common.util.concurrent.RateLimiter;
import com.mojang.realmsclient.client.UploadStatus;
import net.minecraft.realms.RealmsLevelSummary;
import org.apache.logging.log4j.Logger;
import net.minecraft.realms.RealmsScreen;

public class RealmsUploadScreen extends RealmsScreen {
    private static final Logger LOGGER;
    private final RealmsResetWorldScreen lastScreen;
    private final RealmsLevelSummary selectedLevel;
    private final long worldId;
    private final int slotId;
    private final UploadStatus uploadStatus;
    private final RateLimiter narrationRateLimiter;
    private volatile String errorMessage;
    private volatile String status;
    private volatile String progress;
    private volatile boolean cancelled;
    private volatile boolean uploadFinished;
    private volatile boolean showDots;
    private volatile boolean uploadStarted;
    private RealmsButton backButton;
    private RealmsButton cancelButton;
    private int animTick;
    private static final String[] DOTS;
    private int dotIndex;
    private Long previousWrittenBytes;
    private Long previousTimeSnapshot;
    private long bytesPersSecond;
    private static final ReentrantLock uploadLock;
    
    public RealmsUploadScreen(final long long1, final int integer, final RealmsResetWorldScreen daf, final RealmsLevelSummary realmsLevelSummary) {
        this.showDots = true;
        this.worldId = long1;
        this.slotId = integer;
        this.lastScreen = daf;
        this.selectedLevel = realmsLevelSummary;
        this.uploadStatus = new UploadStatus();
        this.narrationRateLimiter = RateLimiter.create(0.10000000149011612);
    }
    
    @Override
    public void init() {
        this.setKeyboardHandlerSendRepeatsToGui(true);
        this.backButton = new RealmsButton(1, this.width() / 2 - 100, this.height() - 42, 200, 20, RealmsScreen.getLocalizedString("gui.back")) {
            @Override
            public void onPress() {
                RealmsUploadScreen.this.onBack();
            }
        };
        this.buttonsAdd(this.cancelButton = new RealmsButton(0, this.width() / 2 - 100, this.height() - 42, 200, 20, RealmsScreen.getLocalizedString("gui.cancel")) {
            @Override
            public void onPress() {
                RealmsUploadScreen.this.onCancel();
            }
        });
        if (!this.uploadStarted) {
            if (this.lastScreen.slot == -1) {
                this.upload();
            }
            else {
                this.lastScreen.switchSlot(this);
            }
        }
    }
    
    @Override
    public void confirmResult(final boolean boolean1, final int integer) {
        if (boolean1 && !this.uploadStarted) {
            this.uploadStarted = true;
            Realms.setScreen(this);
            this.upload();
        }
    }
    
    @Override
    public void removed() {
        this.setKeyboardHandlerSendRepeatsToGui(false);
    }
    
    private void onBack() {
        this.lastScreen.confirmResult(true, 0);
    }
    
    private void onCancel() {
        this.cancelled = true;
        Realms.setScreen(this.lastScreen);
    }
    
    @Override
    public boolean keyPressed(final int integer1, final int integer2, final int integer3) {
        if (integer1 == 256) {
            if (this.showDots) {
                this.onCancel();
            }
            else {
                this.onBack();
            }
            return true;
        }
        return super.keyPressed(integer1, integer2, integer3);
    }
    
    @Override
    public void render(final int integer1, final int integer2, final float float3) {
        this.renderBackground();
        if (!this.uploadFinished && this.uploadStatus.bytesWritten != 0L && this.uploadStatus.bytesWritten == (long)this.uploadStatus.totalBytes) {
            this.status = RealmsScreen.getLocalizedString("mco.upload.verifying");
            this.cancelButton.active(false);
        }
        this.drawCenteredString(this.status, this.width() / 2, 50, 16777215);
        if (this.showDots) {
            this.drawDots();
        }
        if (this.uploadStatus.bytesWritten != 0L && !this.cancelled) {
            this.drawProgressBar();
            this.drawUploadSpeed();
        }
        if (this.errorMessage != null) {
            final String[] arr5 = this.errorMessage.split("\\\\n");
            for (int integer3 = 0; integer3 < arr5.length; ++integer3) {
                this.drawCenteredString(arr5[integer3], this.width() / 2, 110 + 12 * integer3, 16711680);
            }
        }
        super.render(integer1, integer2, float3);
    }
    
    private void drawDots() {
        final int integer2 = this.fontWidth(this.status);
        if (this.animTick % 10 == 0) {
            ++this.dotIndex;
        }
        this.drawString(RealmsUploadScreen.DOTS[this.dotIndex % RealmsUploadScreen.DOTS.length], this.width() / 2 + integer2 / 2 + 5, 50, 16777215);
    }
    
    private void drawProgressBar() {
        double double2 = this.uploadStatus.bytesWritten / (double)this.uploadStatus.totalBytes * 100.0;
        if (double2 > 100.0) {
            double2 = 100.0;
        }
        this.progress = String.format(Locale.ROOT, "%.1f", double2);
        RenderSystem.color4f(1.0f, 1.0f, 1.0f, 1.0f);
        RenderSystem.disableTexture();
        final double double3 = this.width() / 2 - 100;
        final double double4 = 0.5;
        final Tezzelator tezzelator8 = Tezzelator.instance;
        tezzelator8.begin(7, RealmsDefaultVertexFormat.POSITION_COLOR);
        tezzelator8.vertex(double3 - 0.5, 95.5, 0.0).color(217, 210, 210, 255).endVertex();
        tezzelator8.vertex(double3 + 200.0 * double2 / 100.0 + 0.5, 95.5, 0.0).color(217, 210, 210, 255).endVertex();
        tezzelator8.vertex(double3 + 200.0 * double2 / 100.0 + 0.5, 79.5, 0.0).color(217, 210, 210, 255).endVertex();
        tezzelator8.vertex(double3 - 0.5, 79.5, 0.0).color(217, 210, 210, 255).endVertex();
        tezzelator8.vertex(double3, 95.0, 0.0).color(128, 128, 128, 255).endVertex();
        tezzelator8.vertex(double3 + 200.0 * double2 / 100.0, 95.0, 0.0).color(128, 128, 128, 255).endVertex();
        tezzelator8.vertex(double3 + 200.0 * double2 / 100.0, 80.0, 0.0).color(128, 128, 128, 255).endVertex();
        tezzelator8.vertex(double3, 80.0, 0.0).color(128, 128, 128, 255).endVertex();
        tezzelator8.end();
        RenderSystem.enableTexture();
        this.drawCenteredString(this.progress + " %", this.width() / 2, 84, 16777215);
    }
    
    private void drawUploadSpeed() {
        if (this.animTick % 20 == 0) {
            if (this.previousWrittenBytes != null) {
                long long2 = System.currentTimeMillis() - this.previousTimeSnapshot;
                if (long2 == 0L) {
                    long2 = 1L;
                }
                this.drawUploadSpeed0(this.bytesPersSecond = 1000L * (this.uploadStatus.bytesWritten - this.previousWrittenBytes) / long2);
            }
            this.previousWrittenBytes = this.uploadStatus.bytesWritten;
            this.previousTimeSnapshot = System.currentTimeMillis();
        }
        else {
            this.drawUploadSpeed0(this.bytesPersSecond);
        }
    }
    
    private void drawUploadSpeed0(final long long1) {
        if (long1 > 0L) {
            final int integer4 = this.fontWidth(this.progress);
            final String string5 = "(" + humanReadableByteCount(long1) + ")";
            this.drawString(string5, this.width() / 2 + integer4 / 2 + 15, 84, 16777215);
        }
    }
    
    public static String humanReadableByteCount(final long long1) {
        final int integer3 = 1024;
        if (long1 < 1024L) {
            return long1 + " B";
        }
        final int integer4 = (int)(Math.log((double)long1) / Math.log(1024.0));
        final String string5 = "KMGTPE".charAt(integer4 - 1) + "";
        return String.format(Locale.ROOT, "%.1f %sB/s", long1 / Math.pow(1024.0, integer4), string5);
    }
    
    @Override
    public void tick() {
        super.tick();
        ++this.animTick;
        if (this.status != null && this.narrationRateLimiter.tryAcquire(1)) {
            final List<String> list2 = Lists.newArrayList();
            list2.add(this.status);
            if (this.progress != null) {
                list2.add(this.progress + "%");
            }
            if (this.errorMessage != null) {
                list2.add(this.errorMessage);
            }
            Realms.narrateNow(String.join(System.lineSeparator(), list2));
        }
    }
    
    public static Unit getLargestUnit(final long long1) {
        if (long1 < 1024L) {
            return Unit.B;
        }
        final int integer3 = (int)(Math.log((double)long1) / Math.log(1024.0));
        final String string4 = "KMGTPE".charAt(integer3 - 1) + "";
        try {
            return Unit.valueOf(string4 + "B");
        }
        catch (Exception exception5) {
            return Unit.GB;
        }
    }
    
    public static double convertToUnit(final long long1, final Unit a) {
        if (a.equals(Unit.B)) {
            return (double)long1;
        }
        return long1 / Math.pow(1024.0, a.ordinal());
    }
    
    public static String humanReadableSize(final long long1, final Unit a) {
        return String.format("%." + (a.equals(Unit.GB) ? "1" : "0") + "f %s", convertToUnit(long1, a), a.name());
    }
    
    private void upload() {
        this.uploadStarted = true;
        File file2;
        final RealmsClient cyy3;
        final long long4;
        UploadInfo uploadInfo6;
        int integer7;
        File file3;
        long long5;
        Unit a10;
        Unit a11;
        Unit a12;
        FileUpload cyw8;
        final long long6;
        new Thread(() -> {
            file2 = null;
            cyy3 = RealmsClient.createRealmsClient();
            long4 = this.worldId;
            try {
                if (!(!RealmsUploadScreen.uploadLock.tryLock(1L, TimeUnit.SECONDS))) {
                    this.status = RealmsScreen.getLocalizedString("mco.upload.preparing");
                    uploadInfo6 = null;
                    integer7 = 0;
                    while (integer7 < 20) {
                        try {
                            if (this.cancelled) {
                                this.uploadCancelled();
                                return;
                            }
                            else {
                                uploadInfo6 = cyy3.upload(long4, UploadTokenCache.get(long4));
                            }
                        }
                        catch (RetryCallException czh8) {
                            Thread.sleep(czh8.delaySeconds * 1000);
                            ++integer7;
                            continue;
                        }
                        break;
                    }
                    if (uploadInfo6 == null) {
                        this.status = RealmsScreen.getLocalizedString("mco.upload.close.failure");
                    }
                    else {
                        UploadTokenCache.put(long4, uploadInfo6.getToken());
                        if (!uploadInfo6.isWorldClosed()) {
                            this.status = RealmsScreen.getLocalizedString("mco.upload.close.failure");
                        }
                        else if (this.cancelled) {
                            this.uploadCancelled();
                        }
                        else {
                            file3 = new File(Realms.getGameDirectoryPath(), "saves");
                            file2 = this.tarGzipArchive(new File(file3, this.selectedLevel.getLevelId()));
                            if (this.cancelled) {
                                this.uploadCancelled();
                            }
                            else if (!this.verify(file2)) {
                                long5 = file2.length();
                                a10 = getLargestUnit(long5);
                                a11 = getLargestUnit(5368709120L);
                                if (humanReadableSize(long5, a10).equals(humanReadableSize(5368709120L, a11)) && a10 != Unit.B) {
                                    a12 = Unit.values()[a10.ordinal() - 1];
                                    this.errorMessage = RealmsScreen.getLocalizedString("mco.upload.size.failure.line1", this.selectedLevel.getLevelName()) + "\\n" + RealmsScreen.getLocalizedString("mco.upload.size.failure.line2", humanReadableSize(long5, a12), humanReadableSize(5368709120L, a12));
                                }
                                else {
                                    this.errorMessage = RealmsScreen.getLocalizedString("mco.upload.size.failure.line1", this.selectedLevel.getLevelName()) + "\\n" + RealmsScreen.getLocalizedString("mco.upload.size.failure.line2", humanReadableSize(long5, a10), humanReadableSize(5368709120L, a11));
                                }
                            }
                            else {
                                this.status = RealmsScreen.getLocalizedString("mco.upload.uploading", this.selectedLevel.getLevelName());
                                cyw8 = new FileUpload(file2, this.worldId, this.slotId, uploadInfo6, Realms.getSessionId(), Realms.getName(), Realms.getMinecraftVersionString(), this.uploadStatus);
                                cyw8.upload(dap -> {
                                    if (dap.statusCode >= 200 && dap.statusCode < 300) {
                                        this.uploadFinished = true;
                                        this.status = RealmsScreen.getLocalizedString("mco.upload.done");
                                        this.backButton.setMessage(RealmsScreen.getLocalizedString("gui.done"));
                                        UploadTokenCache.invalidate(long6);
                                    }
                                    else if (dap.statusCode == 400 && dap.errorMessage != null) {
                                        this.errorMessage = RealmsScreen.getLocalizedString("mco.upload.failed", dap.errorMessage);
                                    }
                                    else {
                                        this.errorMessage = RealmsScreen.getLocalizedString("mco.upload.failed", dap.statusCode);
                                    }
                                    return;
                                });
                                while (!cyw8.isFinished()) {
                                    if (this.cancelled) {
                                        cyw8.cancel();
                                        this.uploadCancelled();
                                    }
                                    else {
                                        try {
                                            Thread.sleep(500L);
                                        }
                                        catch (InterruptedException interruptedException9) {
                                            RealmsUploadScreen.LOGGER.error("Failed to check Realms file upload status");
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            catch (IOException iOException6) {
                this.errorMessage = RealmsScreen.getLocalizedString("mco.upload.failed", iOException6.getMessage());
            }
            catch (RealmsServiceException czg6) {
                this.errorMessage = RealmsScreen.getLocalizedString("mco.upload.failed", czg6.toString());
            }
            catch (InterruptedException interruptedException10) {
                RealmsUploadScreen.LOGGER.error("Could not acquire upload lock");
            }
            finally {
                this.uploadFinished = true;
                if (RealmsUploadScreen.uploadLock.isHeldByCurrentThread()) {
                    RealmsUploadScreen.uploadLock.unlock();
                    this.showDots = false;
                    this.childrenClear();
                    this.buttonsAdd(this.backButton);
                    if (file2 != null) {
                        RealmsUploadScreen.LOGGER.debug("Deleting file " + file2.getAbsolutePath());
                        file2.delete();
                    }
                }
            }
        }).start();
    }
    
    private void uploadCancelled() {
        this.status = RealmsScreen.getLocalizedString("mco.upload.cancelled");
        RealmsUploadScreen.LOGGER.debug("Upload was cancelled");
    }
    
    private boolean verify(final File file) {
        return file.length() < 5368709120L;
    }
    
    private File tarGzipArchive(final File file) throws IOException {
        TarArchiveOutputStream tarArchiveOutputStream3 = null;
        try {
            final File file2 = File.createTempFile("realms-upload-file", ".tar.gz");
            tarArchiveOutputStream3 = new TarArchiveOutputStream((OutputStream)new GZIPOutputStream(new FileOutputStream(file2)));
            tarArchiveOutputStream3.setLongFileMode(3);
            this.addFileToTarGz(tarArchiveOutputStream3, file.getAbsolutePath(), "world", true);
            tarArchiveOutputStream3.finish();
            return file2;
        }
        finally {
            if (tarArchiveOutputStream3 != null) {
                tarArchiveOutputStream3.close();
            }
        }
    }
    
    private void addFileToTarGz(final TarArchiveOutputStream tarArchiveOutputStream, final String string2, final String string3, final boolean boolean4) throws IOException {
        if (this.cancelled) {
            return;
        }
        final File file6 = new File(string2);
        final String string4 = boolean4 ? string3 : (string3 + file6.getName());
        final TarArchiveEntry tarArchiveEntry8 = new TarArchiveEntry(file6, string4);
        tarArchiveOutputStream.putArchiveEntry((ArchiveEntry)tarArchiveEntry8);
        if (file6.isFile()) {
            IOUtils.copy((InputStream)new FileInputStream(file6), (OutputStream)tarArchiveOutputStream);
            tarArchiveOutputStream.closeArchiveEntry();
        }
        else {
            tarArchiveOutputStream.closeArchiveEntry();
            final File[] arr9 = file6.listFiles();
            if (arr9 != null) {
                for (final File file7 : arr9) {
                    this.addFileToTarGz(tarArchiveOutputStream, file7.getAbsolutePath(), string4 + "/", false);
                }
            }
        }
    }
    
    static {
        LOGGER = LogManager.getLogger();
        DOTS = new String[] { "", ".", ". .", ". . ." };
        uploadLock = new ReentrantLock();
    }
    
    enum Unit {
        B, 
        KB, 
        MB, 
        GB;
    }
}
