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 net.minecraft.SharedConstants;
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 net.minecraft.realms.NarrationHelper;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
import com.google.common.collect.Lists;
import com.mojang.realmsclient.Unit;
import net.minecraft.Util;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.Tesselator;
import com.mojang.blaze3d.systems.RenderSystem;
import java.util.Locale;
import net.minecraft.network.chat.FormattedText;
import net.minecraft.network.chat.TranslatableComponent;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.client.gui.components.Button;
import net.minecraft.network.chat.Component;
import com.google.common.util.concurrent.RateLimiter;
import com.mojang.realmsclient.client.UploadStatus;
import net.minecraft.world.level.storage.LevelSummary;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.logging.log4j.Logger;
import net.minecraft.realms.RealmsScreen;

public class RealmsUploadScreen extends RealmsScreen {
    private static final Logger LOGGER;
    private static final ReentrantLock UPLOAD_LOCK;
    private static final String[] DOTS;
    private final RealmsResetWorldScreen lastScreen;
    private final LevelSummary selectedLevel;
    private final long worldId;
    private final int slotId;
    private final UploadStatus uploadStatus;
    private final RateLimiter narrationRateLimiter;
    private volatile Component[] errorMessage;
    private volatile Component status;
    private volatile String progress;
    private volatile boolean cancelled;
    private volatile boolean uploadFinished;
    private volatile boolean showDots;
    private volatile boolean uploadStarted;
    private Button backButton;
    private Button cancelButton;
    private int tickCount;
    private Long previousWrittenBytes;
    private Long previousTimeSnapshot;
    private long bytesPersSecond;
    private final Runnable callback;
    
    public RealmsUploadScreen(final long long1, final int integer, final RealmsResetWorldScreen dke, final LevelSummary daf, final Runnable runnable) {
        this.showDots = true;
        this.worldId = long1;
        this.slotId = integer;
        this.lastScreen = dke;
        this.selectedLevel = daf;
        this.uploadStatus = new UploadStatus();
        this.narrationRateLimiter = RateLimiter.create(0.10000000149011612);
        this.callback = runnable;
    }
    
    public void init() {
        this.minecraft.keyboardHandler.setSendRepeatsToGui(true);
        this.backButton = new Button(this.width / 2 - 100, this.height - 42, 200, 20, CommonComponents.GUI_BACK, dni -> this.onBack());
        this.cancelButton = this.<Button>addButton(new Button(this.width / 2 - 100, this.height - 42, 200, 20, CommonComponents.GUI_CANCEL, dni -> this.onCancel()));
        if (!this.uploadStarted) {
            if (this.lastScreen.slot == -1) {
                this.upload();
            }
            else {
                this.lastScreen.switchSlot(() -> {
                    if (!this.uploadStarted) {
                        this.uploadStarted = true;
                        this.minecraft.setScreen(this);
                        this.upload();
                    }
                });
            }
        }
    }
    
    @Override
    public void removed() {
        this.minecraft.keyboardHandler.setSendRepeatsToGui(false);
    }
    
    private void onBack() {
        this.callback.run();
    }
    
    private void onCancel() {
        this.cancelled = true;
        this.minecraft.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 PoseStack dhl, final int integer2, final int integer3, final float float4) {
        this.renderBackground(dhl);
        if (!this.uploadFinished && this.uploadStatus.bytesWritten != 0L && this.uploadStatus.bytesWritten == (long)this.uploadStatus.totalBytes) {
            this.status = new TranslatableComponent("mco.upload.verifying");
            this.cancelButton.active = false;
        }
        this.drawCenteredString(dhl, this.font, this.status, this.width / 2, 50, 16777215);
        if (this.showDots) {
            this.drawDots(dhl);
        }
        if (this.uploadStatus.bytesWritten != 0L && !this.cancelled) {
            this.drawProgressBar(dhl);
            this.drawUploadSpeed(dhl);
        }
        if (this.errorMessage != null) {
            for (int integer4 = 0; integer4 < this.errorMessage.length; ++integer4) {
                this.drawCenteredString(dhl, this.font, this.errorMessage[integer4], this.width / 2, 110 + 12 * integer4, 16711680);
            }
        }
        super.render(dhl, integer2, integer3, float4);
    }
    
    private void drawDots(final PoseStack dhl) {
        final int integer3 = this.font.width(this.status);
        this.font.draw(dhl, RealmsUploadScreen.DOTS[this.tickCount / 10 % RealmsUploadScreen.DOTS.length], (float)(this.width / 2 + integer3 / 2 + 5), 50.0f, 16777215);
    }
    
    private void drawProgressBar(final PoseStack dhl) {
        double double3 = this.uploadStatus.bytesWritten / (double)this.uploadStatus.totalBytes * 100.0;
        if (double3 > 100.0) {
            double3 = 100.0;
        }
        this.progress = String.format(Locale.ROOT, "%.1f", double3);
        RenderSystem.color4f(1.0f, 1.0f, 1.0f, 1.0f);
        RenderSystem.disableTexture();
        final double double4 = this.width / 2 - 100;
        final double double5 = 0.5;
        final Tesselator dhn9 = Tesselator.getInstance();
        final BufferBuilder dhg10 = dhn9.getBuilder();
        dhg10.begin(7, DefaultVertexFormat.POSITION_COLOR);
        dhg10.vertex(double4 - 0.5, 95.5, 0.0).color(217, 210, 210, 255).endVertex();
        dhg10.vertex(double4 + 200.0 * double3 / 100.0 + 0.5, 95.5, 0.0).color(217, 210, 210, 255).endVertex();
        dhg10.vertex(double4 + 200.0 * double3 / 100.0 + 0.5, 79.5, 0.0).color(217, 210, 210, 255).endVertex();
        dhg10.vertex(double4 - 0.5, 79.5, 0.0).color(217, 210, 210, 255).endVertex();
        dhg10.vertex(double4, 95.0, 0.0).color(128, 128, 128, 255).endVertex();
        dhg10.vertex(double4 + 200.0 * double3 / 100.0, 95.0, 0.0).color(128, 128, 128, 255).endVertex();
        dhg10.vertex(double4 + 200.0 * double3 / 100.0, 80.0, 0.0).color(128, 128, 128, 255).endVertex();
        dhg10.vertex(double4, 80.0, 0.0).color(128, 128, 128, 255).endVertex();
        dhn9.end();
        RenderSystem.enableTexture();
        this.drawCenteredString(dhl, this.font, this.progress + " %", this.width / 2, 84, 16777215);
    }
    
    private void drawUploadSpeed(final PoseStack dhl) {
        if (this.tickCount % 20 == 0) {
            if (this.previousWrittenBytes != null) {
                long long3 = Util.getMillis() - this.previousTimeSnapshot;
                if (long3 == 0L) {
                    long3 = 1L;
                }
                this.drawUploadSpeed0(dhl, this.bytesPersSecond = 1000L * (this.uploadStatus.bytesWritten - this.previousWrittenBytes) / long3);
            }
            this.previousWrittenBytes = this.uploadStatus.bytesWritten;
            this.previousTimeSnapshot = Util.getMillis();
        }
        else {
            this.drawUploadSpeed0(dhl, this.bytesPersSecond);
        }
    }
    
    private void drawUploadSpeed0(final PoseStack dhl, final long long2) {
        if (long2 > 0L) {
            final int integer5 = this.font.width(this.progress);
            final String string6 = "(" + Unit.humanReadable(long2) + "/s)";
            this.font.draw(dhl, string6, (float)(this.width / 2 + integer5 / 2 + 15), 84.0f, 16777215);
        }
    }
    
    @Override
    public void tick() {
        super.tick();
        ++this.tickCount;
        if (this.status != null && this.narrationRateLimiter.tryAcquire(1)) {
            final List<String> list2 = Lists.newArrayList();
            list2.add(this.status.getString());
            if (this.progress != null) {
                list2.add(this.progress + "%");
            }
            if (this.errorMessage != null) {
                Stream.<Component>of(this.errorMessage).map(Component::getString).forEach(list2::add);
            }
            NarrationHelper.now(String.join(System.lineSeparator(), list2));
        }
    }
    
    private void upload() {
        this.uploadStarted = true;
        File file2;
        final RealmsClient dia3;
        final long long4;
        UploadInfo dja6;
        int integer7;
        File file3;
        long long5;
        Unit dhw10;
        Unit dhw11;
        Unit dhw12;
        Component[] errorMessage;
        final TranslatableComponent translatableComponent;
        final Object o;
        final TranslatableComponent translatableComponent2;
        final Object o2;
        Component[] errorMessage2;
        final TranslatableComponent translatableComponent3;
        final Object o3;
        final TranslatableComponent translatableComponent4;
        final Object o4;
        final TranslatableComponent status;
        FileUpload dhy8;
        final long long6;
        Component[] errorMessage3;
        final Component component;
        final Object o5;
        Component[] errorMessage4;
        final Component component2;
        final Object o6;
        Component[] errorMessage5;
        final TranslatableComponent translatableComponent5;
        final Object o7;
        Component[] errorMessage6;
        final TranslatableComponent translatableComponent6;
        final Object o8;
        new Thread(() -> {
            file2 = null;
            dia3 = RealmsClient.create();
            long4 = this.worldId;
            try {
                if (!(!RealmsUploadScreen.UPLOAD_LOCK.tryLock(1L, TimeUnit.SECONDS))) {
                    this.status = new TranslatableComponent("mco.upload.preparing");
                    dja6 = null;
                    integer7 = 0;
                    while (integer7 < 20) {
                        try {
                            if (this.cancelled) {
                                this.uploadCancelled();
                                return;
                            }
                            else {
                                dja6 = dia3.upload(long4, UploadTokenCache.get(long4));
                            }
                        }
                        catch (RetryCallException dji8) {
                            Thread.sleep(dji8.delaySeconds * 1000);
                            ++integer7;
                            continue;
                        }
                        break;
                    }
                    if (dja6 == null) {
                        this.status = new TranslatableComponent("mco.upload.close.failure");
                    }
                    else {
                        UploadTokenCache.put(long4, dja6.getToken());
                        if (!dja6.isWorldClosed()) {
                            this.status = new TranslatableComponent("mco.upload.close.failure");
                        }
                        else if (this.cancelled) {
                            this.uploadCancelled();
                        }
                        else {
                            file3 = new File(this.minecraft.gameDirectory.getAbsolutePath(), "saves");
                            file2 = this.tarGzipArchive(new File(file3, this.selectedLevel.getLevelId()));
                            if (this.cancelled) {
                                this.uploadCancelled();
                            }
                            else if (!this.verify(file2)) {
                                long5 = file2.length();
                                dhw10 = Unit.getLargest(long5);
                                dhw11 = Unit.getLargest(5368709120L);
                                if (Unit.humanReadable(long5, dhw10).equals(Unit.humanReadable(5368709120L, dhw11)) && dhw10 != Unit.B) {
                                    dhw12 = Unit.values()[dhw10.ordinal() - 1];
                                    errorMessage = new Component[2];
                                    new TranslatableComponent("mco.upload.size.failure.line1", new Object[] { this.selectedLevel.getLevelName() });
                                    errorMessage[o] = translatableComponent;
                                    new TranslatableComponent("mco.upload.size.failure.line2", new Object[] { Unit.humanReadable(long5, dhw12), Unit.humanReadable(5368709120L, dhw12) });
                                    errorMessage[o2] = translatableComponent2;
                                    this.setErrorMessage(errorMessage);
                                }
                                else {
                                    errorMessage2 = new Component[2];
                                    new TranslatableComponent("mco.upload.size.failure.line1", new Object[] { this.selectedLevel.getLevelName() });
                                    errorMessage2[o3] = translatableComponent3;
                                    new TranslatableComponent("mco.upload.size.failure.line2", new Object[] { Unit.humanReadable(long5, dhw10), Unit.humanReadable(5368709120L, dhw11) });
                                    errorMessage2[o4] = translatableComponent4;
                                    this.setErrorMessage(errorMessage2);
                                }
                            }
                            else {
                                new TranslatableComponent("mco.upload.uploading", new Object[] { this.selectedLevel.getLevelName() });
                                this.status = status;
                                dhy8 = new FileUpload(file2, this.worldId, this.slotId, dja6, this.minecraft.getUser(), SharedConstants.getCurrentVersion().getName(), this.uploadStatus);
                                dhy8.upload(dkn -> {
                                    if (dkn.statusCode >= 200 && dkn.statusCode < 300) {
                                        this.uploadFinished = true;
                                        this.status = new TranslatableComponent("mco.upload.done");
                                        this.backButton.setMessage(CommonComponents.GUI_DONE);
                                        UploadTokenCache.invalidate(long6);
                                    }
                                    else if (dkn.statusCode == 400 && dkn.errorMessage != null) {
                                        errorMessage3 = new Component[] { null };
                                        new TranslatableComponent("mco.upload.failed", new Object[] { dkn.errorMessage });
                                        errorMessage3[o5] = component;
                                        this.setErrorMessage(errorMessage3);
                                    }
                                    else {
                                        errorMessage4 = new Component[] { null };
                                        new TranslatableComponent("mco.upload.failed", new Object[] { dkn.statusCode });
                                        errorMessage4[o6] = component2;
                                        this.setErrorMessage(errorMessage4);
                                    }
                                    return;
                                });
                                while (!dhy8.isFinished()) {
                                    if (this.cancelled) {
                                        dhy8.cancel();
                                        this.uploadCancelled();
                                    }
                                    else {
                                        try {
                                            Thread.sleep(500L);
                                        }
                                        catch (InterruptedException interruptedException9) {
                                            RealmsUploadScreen.LOGGER.error("Failed to check Realms file upload status");
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            catch (IOException iOException6) {
                errorMessage5 = new Component[] { null };
                new TranslatableComponent("mco.upload.failed", new Object[] { iOException6.getMessage() });
                errorMessage5[o7] = translatableComponent5;
                this.setErrorMessage(errorMessage5);
            }
            catch (RealmsServiceException djh6) {
                errorMessage6 = new Component[] { null };
                new TranslatableComponent("mco.upload.failed", new Object[] { djh6.toString() });
                errorMessage6[o8] = translatableComponent6;
                this.setErrorMessage(errorMessage6);
            }
            catch (InterruptedException interruptedException10) {
                RealmsUploadScreen.LOGGER.error("Could not acquire upload lock");
            }
            finally {
                this.uploadFinished = true;
                if (RealmsUploadScreen.UPLOAD_LOCK.isHeldByCurrentThread()) {
                    RealmsUploadScreen.UPLOAD_LOCK.unlock();
                    this.showDots = false;
                    this.children.clear();
                    this.<Button>addButton(this.backButton);
                    if (file2 != null) {
                        RealmsUploadScreen.LOGGER.debug("Deleting file " + file2.getAbsolutePath());
                        file2.delete();
                    }
                }
            }
        }).start();
    }
    
    private void setErrorMessage(final Component... arr) {
        this.errorMessage = arr;
    }
    
    private void uploadCancelled() {
        this.status = new TranslatableComponent("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();
        UPLOAD_LOCK = new ReentrantLock();
        DOTS = new String[] { "", ".", ". .", ". . ." };
    }
}
