/*
 * Decompiled with CFR 0.152.
 * 
 * Could not load the following classes:
 *  com.google.common.net.InetAddresses
 *  net.minecraft.class_2487
 *  net.minecraft.class_2520
 */
package meteordevelopment.meteorclient.systems.proxies;

import com.google.common.net.InetAddresses;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.time.Instant;
import java.util.Objects;
import meteordevelopment.meteorclient.settings.BoolSetting;
import meteordevelopment.meteorclient.settings.EnumSetting;
import meteordevelopment.meteorclient.settings.IntSetting;
import meteordevelopment.meteorclient.settings.Setting;
import meteordevelopment.meteorclient.settings.SettingGroup;
import meteordevelopment.meteorclient.settings.Settings;
import meteordevelopment.meteorclient.settings.StringSetting;
import meteordevelopment.meteorclient.systems.proxies.Proxies;
import meteordevelopment.meteorclient.systems.proxies.ProxyType;
import meteordevelopment.meteorclient.utils.Utils;
import meteordevelopment.meteorclient.utils.misc.ISerializable;
import meteordevelopment.meteorclient.utils.render.color.Color;
import net.minecraft.class_2487;
import net.minecraft.class_2520;

public class Proxy
implements ISerializable<Proxy> {
    public final Settings settings = new Settings();
    private final SettingGroup sgGeneral = this.settings.getDefaultGroup();
    private final SettingGroup sgOptional = this.settings.createGroup("Optional");
    public Setting<String> name = this.sgGeneral.add(((StringSetting.Builder)((StringSetting.Builder)new StringSetting.Builder().name("name")).description("The name of the proxy.")).build());
    public Setting<ProxyType> type = this.sgGeneral.add(((EnumSetting.Builder)((EnumSetting.Builder)((EnumSetting.Builder)new EnumSetting.Builder().name("type")).description("The type of proxy.")).defaultValue(ProxyType.Socks5)).build());
    public Setting<String> address = this.sgGeneral.add(((StringSetting.Builder)((StringSetting.Builder)new StringSetting.Builder().name("address")).description("The ip address of the proxy.")).filter(Utils::ipFilter).build());
    public Setting<Integer> port = this.sgGeneral.add(((IntSetting.Builder)((IntSetting.Builder)((IntSetting.Builder)new IntSetting.Builder().name("port")).description("The port of the proxy.")).defaultValue(0)).range(0, 65535).sliderMax(65535).noSlider().build());
    public Setting<Boolean> enabled = this.sgGeneral.add(((BoolSetting.Builder)((BoolSetting.Builder)((BoolSetting.Builder)new BoolSetting.Builder().name("enabled")).description("Whether the proxy is enabled.")).defaultValue(true)).build());
    public Setting<String> username = this.sgOptional.add(((StringSetting.Builder)((StringSetting.Builder)new StringSetting.Builder().name("username")).description("The username of the proxy.")).build());
    public Setting<String> password = this.sgOptional.add(((StringSetting.Builder)((StringSetting.Builder)((StringSetting.Builder)new StringSetting.Builder().name("password")).description("The password of the proxy.")).visible(() -> this.type.get().equals((Object)ProxyType.Socks5))).build());
    public Status status = Status.UNCHECKED;
    public long latency;

    private Proxy() {
    }

    public Proxy(class_2520 tag) {
        this.fromTag((class_2487)tag);
    }

    public boolean resolveAddress() {
        return Utils.resolveAddress(this.address.get(), this.port.get());
    }

    public int checkStatus() {
        Instant before;
        if (this.status == Status.CHECKING) {
            return 0;
        }
        this.status = Status.CHECKING;
        boolean timeout = false;
        try {
            before = Instant.now();
            if (this.isSocks4()) {
                this.status = Status.ALIVE;
                this.latency = Duration.between(before, Instant.now()).toMillis();
                return 1;
            }
        }
        catch (SocketTimeoutException e) {
            timeout = true;
        }
        catch (IOException e) {
            // empty catch block
        }
        try {
            before = Instant.now();
            if (this.isSocks5()) {
                this.status = Status.ALIVE;
                this.latency = Duration.between(before, Instant.now()).toMillis();
                return 1;
            }
        }
        catch (SocketTimeoutException e) {
            timeout = true;
        }
        catch (IOException iOException) {
            // empty catch block
        }
        this.status = Status.DEAD;
        return timeout ? 3 : 2;
    }

    private boolean isSocks4() throws IOException {
        ByteBuffer bb;
        byte[] u = this.username.get().getBytes();
        if (InetAddresses.isInetAddress((String)this.address.get())) {
            bb = ByteBuffer.allocate(9 + u.length).put((byte)4).put((byte)1).putShort(this.port.get().shortValue()).putInt(InetAddress.getByName(this.address.get()).hashCode()).put(u).put((byte)0);
        } else {
            byte[] addr = this.address.get().getBytes();
            bb = ByteBuffer.allocate(10 + u.length + addr.length).put((byte)4).put((byte)1).putShort(this.port.get().shortValue()).put(new byte[]{0, 0, 0, 1}).put(u).put((byte)0).put(addr).put((byte)0);
        }
        byte[] data = this.sendData(bb.array(), 8);
        if (data.length < 2) {
            return false;
        }
        return data[0] == 0 && data[1] == 90;
    }

    private boolean isSocks5() throws IOException {
        ByteBuffer bb = ByteBuffer.allocate(4).put((byte)5).put((byte)2).put((byte)0).put((byte)2);
        byte[] data = this.sendData(bb.array(), 2);
        if (data.length < 2) {
            return false;
        }
        return data[0] == 5 && (data[1] == 0 || data[1] == 2);
    }

    private byte[] sendData(byte[] data, int read) throws IOException {
        try (Socket s = new Socket();){
            s.setSoTimeout(Proxies.get().timeout.get());
            s.connect(new InetSocketAddress(this.address.get(), (int)this.port.get()), Proxies.get().timeout.get());
            OutputStream out = s.getOutputStream();
            out.write(data);
            byte[] byArray = s.getInputStream().readNBytes(read);
            return byArray;
        }
    }

    @Override
    public class_2487 toTag() {
        class_2487 tag = new class_2487();
        tag.method_10566("settings", (class_2520)this.settings.toTag());
        return tag;
    }

    @Override
    public Proxy fromTag(class_2487 tag) {
        tag.method_10562("settings").ifPresent(this.settings::fromTag);
        return this;
    }

    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || this.getClass() != o.getClass()) {
            return false;
        }
        Proxy proxy = (Proxy)o;
        return Objects.equals(proxy.address.get(), this.address.get()) && Objects.equals(proxy.port.get(), this.port.get());
    }

    public static enum Status {
        UNCHECKED,
        CHECKING,
        DEAD,
        ALIVE;


        public String toString() {
            return switch (this.ordinal()) {
                default -> throw new MatchException(null, null);
                case 0 -> "";
                case 1 -> "...";
                case 2 -> "X";
                case 3 -> "O";
            };
        }

        public Color getColor() {
            return switch (this.ordinal()) {
                default -> throw new MatchException(null, null);
                case 0, 1 -> Color.GRAY;
                case 2 -> Color.RED;
                case 3 -> Color.GREEN;
            };
        }
    }

    public static class Builder {
        protected ProxyType type = ProxyType.Socks5;
        protected String address = "";
        protected int port = 0;
        protected String name = "";
        protected String username = "";
        protected String password = "";
        protected boolean enabled = false;

        public Builder type(ProxyType type) {
            this.type = type;
            return this;
        }

        public Builder address(String address) {
            this.address = address;
            return this;
        }

        public Builder port(int port) {
            this.port = port;
            return this;
        }

        public Builder name(String name) {
            this.name = name;
            return this;
        }

        public Builder username(String username) {
            this.username = username;
            return this;
        }

        public Builder password(String password) {
            this.password = password;
            return this;
        }

        public Builder enabled(boolean enabled) {
            this.enabled = enabled;
            return this;
        }

        public Proxy build() {
            Proxy proxy = new Proxy();
            if (!this.type.equals((Object)proxy.type.getDefaultValue())) {
                proxy.type.set(this.type);
            }
            if (!this.address.equals(proxy.address.getDefaultValue())) {
                proxy.address.set(this.address);
            }
            if (this.port != proxy.port.getDefaultValue()) {
                proxy.port.set(this.port);
            }
            if (!this.name.equals(proxy.name.getDefaultValue())) {
                proxy.name.set(this.name);
            }
            if (!this.username.equals(proxy.username.getDefaultValue())) {
                proxy.username.set(this.username);
            }
            if (!this.password.equals(proxy.password.getDefaultValue())) {
                proxy.password.set(this.password);
            }
            if (this.enabled != proxy.enabled.getDefaultValue()) {
                proxy.enabled.set(this.enabled);
            }
            return proxy;
        }
    }
}

