package com.dragontcp.client;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.GradientDrawable;
import android.net.VpnService;
import android.os.Build;
import android.os.Bundle;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {
    private static final int VPN_REQUEST = 100;
    private static final String PREFS = "dragontcp";

    private static final int BATCH_LIMIT = 256;

    private static final int BG = Color.rgb(11, 15, 20);
    private static final int CARD = Color.rgb(22, 28, 36);
    private static final int FIELD = Color.rgb(14, 19, 26);
    private static final int BORDER = Color.rgb(43, 53, 66);
    private static final int TEXT = Color.rgb(244, 247, 251);
    private static final int MUTED = Color.rgb(139, 151, 168);
    private static final int ACCENT = Color.rgb(92, 149, 255);
    private static final int STOP = Color.rgb(218, 83, 83);
    private static final int DISABLED = Color.rgb(48, 56, 68);
    private static final int OK = Color.rgb(82, 201, 143);
    private static final int WARN = Color.rgb(240, 177, 83);
    private static final int BAD = Color.rgb(232, 116, 116);

    private EditText server;
    private EditText port;
    private EditText token;
    private EditText chunkMax;
    private EditText chunkMin;
    private EditText batchMax;
    private EditText batchMin;
    private EditText reconnect;
    private Button wireAuto;
    private Button wireB;
    private Button wireX;
    private String wireMode = "auto";
    private EditText timeout;

    private TextView batchHint;
    private TextView wireHint;
    private Button connectButton;
    private Button stopButton;
    private Button logsButton;
    private TextView statusChip;

    private boolean receiverRegistered;
    private boolean connectPending;

    private final BroadcastReceiver stateReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (!DragonService.ACTION_STATE.equals(intent.getAction())) return;
            boolean active = intent.getBooleanExtra(DragonService.EXTRA_ACTIVE, false);
            String status = intent.getStringExtra(DragonService.EXTRA_STATUS);
            connectPending = false;
            updateConnectionUi(active, status == null ? (active ? "CONNECTED" : "DISCONNECTED") : status);
        }
    };

    @Override
    public void onCreate(Bundle state) {
        super.onCreate(state);
        forceDarkSystemUi();
        buildUi();
        loadSettings();
        updateBatchHint();
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (!receiverRegistered) {
            registerReceiver(stateReceiver, new IntentFilter(DragonService.ACTION_STATE));
            receiverRegistered = true;
        }
        updateConnectionUi(DragonService.isActive(), DragonService.currentStatus());
    }

    @Override
    protected void onStop() {
        if (receiverRegistered) {
            try { unregisterReceiver(stateReceiver); } catch (Throwable ignored) {}
            receiverRegistered = false;
        }
        super.onStop();
    }

    private void forceDarkSystemUi() {
        Window w = getWindow();
        w.setStatusBarColor(BG);
        w.setNavigationBarColor(Color.rgb(7, 10, 14));
        if (Build.VERSION.SDK_INT >= 26) w.getDecorView().setSystemUiVisibility(0);
    }

    private void buildUi() {
        LinearLayout root = new LinearLayout(this);
        root.setOrientation(LinearLayout.VERTICAL);
        root.setBackgroundColor(BG);
        root.setPadding(dp(16), dp(14), dp(16), dp(12));

        LinearLayout header = new LinearLayout(this);
        header.setOrientation(LinearLayout.HORIZONTAL);
        header.setGravity(Gravity.CENTER_VERTICAL);
        header.setPadding(dp(2), dp(2), dp(2), dp(12));

        LinearLayout heading = new LinearLayout(this);
        heading.setOrientation(LinearLayout.VERTICAL);
        TextView title = text("DragonTCP Lite", 25, TEXT, true);
        TextView subtitle = text("Adaptive binary tunnel over TCP/53", 12, MUTED, false);
        subtitle.setPadding(0, dp(2), 0, 0);
        heading.addView(title);
        heading.addView(subtitle);
        header.addView(heading, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f));

        statusChip = text("DISCONNECTED", 11, MUTED, true);
        statusChip.setGravity(Gravity.CENTER);
        statusChip.setPadding(dp(12), dp(7), dp(12), dp(7));
        statusChip.setBackground(roundRect(CARD, 999, BORDER, 1));
        header.addView(statusChip);
        root.addView(header);

        ScrollView scroll = new ScrollView(this);
        scroll.setFillViewport(false);
        scroll.setOverScrollMode(View.OVER_SCROLL_IF_CONTENT_SCROLLS);
        scroll.setVerticalScrollBarEnabled(false);

        LinearLayout settings = new LinearLayout(this);
        settings.setOrientation(LinearLayout.VERTICAL);
        settings.setPadding(0, 0, 0, dp(12));
        scroll.addView(settings, new ScrollView.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT
        ));

        // ---------------------------------------------------------- connection
        LinearLayout connectionCard = card("CONNECTION");
        server = addField(connectionCard, "Server", "Server IP or hostname", "", false, false);
        LinearLayout connectionRow = row();
        port = addFieldToRow(connectionRow, "Port", "53", "53", true, false, 0.34f);
        token = addFieldToRow(connectionRow, "Token", "Optional", "", false, true, 0.66f);
        connectionCard.addView(connectionRow);
        settings.addView(connectionCard, cardParams());

        // ---------------------------------------------------------------- wire
        LinearLayout wireCard = card("WIRE");
        wireCard.addView(hint("Networks differ in which mode they pass."));
        LinearLayout wireRow = row();
        wireAuto = segmentButton("A");
        wireB = segmentButton("B");
        wireX = segmentButton("X");
        addSegment(wireRow, wireAuto);
        addSegment(wireRow, wireB);
        addSegment(wireRow, wireX);
        wireCard.addView(wireRow);
        wireHint = text("", 11, MUTED, true);
        wireHint.setLineSpacing(0, 1.08f);
        wireHint.setPadding(dp(2), dp(8), dp(2), dp(2));
        wireCard.addView(wireHint);
        settings.addView(wireCard, cardParams());

        wireAuto.setOnClickListener(v -> setWireMode("auto"));
        wireB.setOnClickListener(v -> setWireMode("b"));
        wireX.setOnClickListener(v -> setWireMode("x"));

        // --------------------------------------------------------- record size
        LinearLayout sizeCard = card("RECORD SIZE (BYTES)");
        LinearLayout chunks = row();
        chunkMax = addFieldToRow(chunks, "Max chunk", "1048576", "1048576", true, false, 0.58f);
        chunkMin = addFieldToRow(chunks, "Min chunk", "32", "32", true, false, 0.42f);
        sizeCard.addView(chunks);
        sizeCard.addView(hint("Probed automatically on connect, then adapted if the path changes."));
        settings.addView(sizeCard, cardParams());

        // ------------------------------------------------------- download batch
        LinearLayout batchCard = card("DOWNLOAD BATCH");
        batchCard.addView(hint(
                "How many records one download request may return. This is a transport "
                        + "setting, not a thread count."
        ));
        LinearLayout batch = row();
        batchMax = addFieldToRow(batch, "Batch max", "1", "1", true, false, 0.5f);
        batchMin = addFieldToRow(batch, "Batch min", "1", "1", true, false, 0.5f);
        batchCard.addView(batch);

        batchHint = text("", 11, MUTED, true);
        batchHint.setLineSpacing(0, 1.08f);
        batchHint.setPadding(dp(2), dp(2), dp(2), dp(2));
        batchCard.addView(batchHint);
        batchCard.addView(hint(
                "Set both to the same number to pin the batch: the depth never grows or "
                        + "shrinks, which is what paths that only work at one specific size need."
        ));
        settings.addView(batchCard, cardParams());

        TextWatcher batchWatcher = new TextWatcher() {
            @Override public void beforeTextChanged(CharSequence s, int a, int b, int c) {}
            @Override public void onTextChanged(CharSequence s, int a, int b, int c) {}
            @Override public void afterTextChanged(Editable s) { updateBatchHint(); }
        };
        batchMax.addTextChangedListener(batchWatcher);
        batchMin.addTextChangedListener(batchWatcher);

        // ------------------------------------------------------------ advanced
        LinearLayout advancedCard = card("ADVANCED");
        LinearLayout timing = row();
        reconnect = addFieldToRow(timing, "Reconnect every", "1 = auto", "1", true, false, 0.58f);
        timeout = addFieldToRow(timing, "Timeout (s)", "2", "2", true, false, 0.42f);
        advancedCard.addView(timing);
        advancedCard.addView(hint(
                "1 = auto (recommended: probes the path, falls back to one request per "
                        + "connection)  •  0 = persistent  •  N = rotate every N requests"));
        settings.addView(advancedCard, cardParams());

        // ------------------------------------------------------------- buttons
        LinearLayout buttons = row();
        buttons.setPadding(0, dp(4), 0, dp(8));
        connectButton = actionButton("CONNECT");
        stopButton = actionButton("STOP");
        LinearLayout.LayoutParams left = new LinearLayout.LayoutParams(0, dp(52), 1f);
        left.setMarginEnd(dp(6));
        LinearLayout.LayoutParams right = new LinearLayout.LayoutParams(0, dp(52), 1f);
        right.setMarginStart(dp(6));
        buttons.addView(connectButton, left);
        buttons.addView(stopButton, right);
        settings.addView(buttons);

        logsButton = actionButton("OPEN LOGS");
        logsButton.setTextColor(TEXT);
        logsButton.setBackground(roundRect(Color.rgb(32, 40, 51), 14, BORDER, 1));
        LinearLayout.LayoutParams logsParams = new LinearLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, dp(50));
        logsParams.setMargins(0, 0, 0, dp(10));
        settings.addView(logsButton, logsParams);

        TextView note = text(
                "IPv4 uses the Android VPN adapter. IPv6 is captured and blocked to prevent bypass. DNS is tunneled to 1.1.1.1 over DragonTCP.",
                11, MUTED, false
        );
        note.setLineSpacing(0, 1.08f);
        note.setPadding(dp(4), dp(2), dp(4), dp(6));
        settings.addView(note);

        root.addView(scroll, new LinearLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                0,
                1f
        ));

        connectButton.setOnClickListener(v -> requestConnect());
        stopButton.setOnClickListener(v -> requestStop());
        logsButton.setOnClickListener(v -> startActivity(new Intent(this, LogActivity.class)));

        setContentView(root);
        updateConnectionUi(false, "DISCONNECTED");
    }

    /** Describes the batch configuration in words, live, as the user types. */
    private void updateBatchHint() {
        if (batchHint == null) return;
        Integer max = readInt(batchMax);
        Integer min = readInt(batchMin);

        if (max == null || min == null) {
            batchHint.setTextColor(MUTED);
            batchHint.setText("Enter a value from 1 to " + BATCH_LIMIT + ".");
            return;
        }
        if (max < 1 || max > BATCH_LIMIT || min < 1 || min > BATCH_LIMIT) {
            batchHint.setTextColor(BAD);
            batchHint.setText("Batch values must be 1-" + BATCH_LIMIT + ".");
            return;
        }
        if (min > max) {
            batchHint.setTextColor(BAD);
            batchHint.setText("Batch min must not be greater than batch max.");
            return;
        }
        if (min == max) {
            batchHint.setTextColor(OK);
            if (max == 1) {
                batchHint.setText("Fixed: 1 record per request, never adapts.");
            } else {
                batchHint.setText("Pinned: exactly " + max + " records per request, never adapts.");
            }
            return;
        }
        batchHint.setTextColor(ACCENT);
        batchHint.setText("Adaptive: starts at " + max + ", falls back toward " + min
                + " on errors, recovers to " + max + ".");
    }


    private Integer readInt(EditText field) {
        if (field == null) return null;
        String raw = field.getText().toString().trim();
        if (raw.isEmpty()) return null;
        try { return Integer.valueOf(Integer.parseInt(raw)); }
        catch (Exception e) { return null; }
    }

    private LinearLayout card(String title) {
        LinearLayout card = new LinearLayout(this);
        card.setOrientation(LinearLayout.VERTICAL);
        card.setPadding(dp(13), dp(11), dp(13), dp(12));
        card.setBackground(roundRect(CARD, 16, BORDER, 1));
        TextView label = text(title, 11, MUTED, true);
        label.setLetterSpacing(0.08f);
        label.setPadding(dp(2), 0, 0, dp(7));
        card.addView(label);
        return card;
    }

    private TextView hint(String value) {
        TextView t = text(value, 11, MUTED, false);
        t.setLineSpacing(0, 1.08f);
        t.setPadding(dp(2), dp(2), dp(2), dp(6));
        return t;
    }

    private LinearLayout.LayoutParams cardParams() {
        LinearLayout.LayoutParams p = new LinearLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT
        );
        p.setMargins(0, 0, 0, dp(10));
        return p;
    }

    private EditText addField(LinearLayout parent, String label, String hint, String value, boolean numeric, boolean password) {
        LinearLayout block = fieldBlock(label);
        EditText edit = createEdit(hint, value, numeric, password);
        block.addView(edit, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(46)));
        parent.addView(block, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        return edit;
    }

    private EditText addFieldToRow(LinearLayout parent, String label, String hint, String value, boolean numeric, boolean password, float weight) {
        LinearLayout block = fieldBlock(label);
        EditText edit = createEdit(hint, value, numeric, password);
        block.addView(edit, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(46)));
        LinearLayout.LayoutParams p = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, weight);
        if (parent.getChildCount() > 0) p.setMarginStart(dp(8));
        parent.addView(block, p);
        return edit;
    }

    private LinearLayout fieldBlock(String label) {
        LinearLayout block = new LinearLayout(this);
        block.setOrientation(LinearLayout.VERTICAL);
        block.setPadding(0, 0, 0, dp(8));
        TextView name = text(label, 11, MUTED, false);
        name.setPadding(dp(2), 0, 0, dp(4));
        block.addView(name);
        return block;
    }

    private EditText createEdit(String hint, String value, boolean numeric, boolean password) {
        EditText edit = new EditText(this);
        edit.setSingleLine(true);
        edit.setText(value);
        edit.setHint(hint);
        edit.setTextColor(TEXT);
        edit.setHintTextColor(Color.rgb(91, 103, 120));
        edit.setTextSize(15);
        edit.setPadding(dp(12), 0, dp(12), 0);
        edit.setBackground(roundRect(FIELD, 11, BORDER, 1));
        edit.setSelectAllOnFocus(false);
        if (numeric) edit.setInputType(InputType.TYPE_CLASS_NUMBER);
        if (password) edit.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
        return edit;
    }

    private Button segmentButton(String label) {
        Button b = new Button(this);
        b.setText(label);
        b.setTextSize(15);
        b.setTypeface(Typeface.DEFAULT, Typeface.BOLD);
        b.setAllCaps(false);
        return b;
    }

    private void addSegment(LinearLayout parent, Button b) {
        LinearLayout.LayoutParams p = new LinearLayout.LayoutParams(0, dp(46), 1f);
        if (parent.getChildCount() > 0) p.setMarginStart(dp(8));
        parent.addView(b, p);
    }

    /** Selects the wire format and repaints the segmented control. */
    private void setWireMode(String mode) {
        wireMode = mode;
        paintSegment(wireAuto, "auto".equals(mode));
        paintSegment(wireB, "b".equals(mode));
        paintSegment(wireX, "x".equals(mode));
        if (wireHint == null) return;
        if ("auto".equals(mode)) {
            wireHint.setTextColor(ACCENT);
            wireHint.setText("Auto: tries B, then X, keeping the one that connects.");
        } else if ("b".equals(mode)) {
            wireHint.setTextColor(OK);
            wireHint.setText("Manual: B.");
        } else {
            wireHint.setTextColor(OK);
            wireHint.setText("Manual: X.");
        }
    }

    private void paintSegment(Button b, boolean selected) {
        if (b == null) return;
        b.setTextColor(selected ? Color.WHITE : Color.rgb(132, 142, 155));
        b.setBackground(roundRect(selected ? ACCENT : Color.rgb(32, 38, 47), 12,
                selected ? ACCENT : BORDER, 1));
    }

    private LinearLayout row() {
        LinearLayout row = new LinearLayout(this);
        row.setOrientation(LinearLayout.HORIZONTAL);
        return row;
    }

    private TextView text(String value, float size, int color, boolean bold) {
        TextView t = new TextView(this);
        t.setText(value);
        t.setTextSize(size);
        t.setTextColor(color);
        if (bold) t.setTypeface(Typeface.DEFAULT, Typeface.BOLD);
        return t;
    }

    private Button actionButton(String value) {
        Button b = new Button(this);
        b.setText(value);
        b.setTextSize(14);
        b.setTypeface(Typeface.DEFAULT, Typeface.BOLD);
        b.setTextColor(Color.WHITE);
        b.setAllCaps(false);
        return b;
    }

    private GradientDrawable roundRect(int color, int radiusDp, int strokeColor, int strokeDp) {
        GradientDrawable d = new GradientDrawable();
        d.setColor(color);
        d.setCornerRadius(dp(radiusDp));
        if (strokeDp > 0) d.setStroke(dp(strokeDp), strokeColor);
        return d;
    }

    private void requestConnect() {
        if (connectPending || DragonService.isActive()) return;
        try {
            validateAndSave();
        } catch (Exception e) {
            String msg = e.getMessage() == null ? "Invalid configuration" : e.getMessage();
            AppLog.append("CONFIG: " + msg);
            Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
            return;
        }

        connectPending = true;
        updateConnectionUi(true, "CONNECTING");

        Intent prepare = VpnService.prepare(this);
        if (prepare != null) startActivityForResult(prepare, VPN_REQUEST);
        else startDragonService();
    }

    private void requestStop() {
        if (!DragonService.isActive() && !connectPending) return;
        connectPending = false;
        updateConnectionUi(true, "STOPPING");
        connectButton.setEnabled(false);
        stopButton.setEnabled(false);
        Intent i = new Intent(this, DragonService.class).setAction(DragonService.ACTION_STOP);
        startService(i);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == VPN_REQUEST) {
            if (resultCode == RESULT_OK) {
                startDragonService();
            } else {
                connectPending = false;
                updateConnectionUi(false, "DISCONNECTED");
                AppLog.append("VPN permission was not granted");
            }
        }
    }

    private void startDragonService() {
        SharedPreferences p = getSharedPreferences(PREFS, MODE_PRIVATE);
        Intent i = new Intent(this, DragonService.class).setAction(DragonService.ACTION_CONNECT);
        i.putExtra(DragonService.EXTRA_SERVER, p.getString("server", ""));
        i.putExtra(DragonService.EXTRA_PORT, p.getInt("port", 53));
        i.putExtra(DragonService.EXTRA_TOKEN, p.getString("token", ""));
        i.putExtra(DragonService.EXTRA_WIRE, p.getString("wire", "auto"));
        i.putExtra(DragonService.EXTRA_CHUNK_MAX, p.getInt("max", 1048576));
        i.putExtra(DragonService.EXTRA_CHUNK_MIN, p.getInt("min", 32));
        i.putExtra(DragonService.EXTRA_BATCH_MAX, p.getInt("batchMax", 1));
        i.putExtra(DragonService.EXTRA_BATCH_MIN, p.getInt("batchMin", 1));
        i.putExtra(DragonService.EXTRA_RECONNECT, p.getInt("reconnect", 1));
        i.putExtra(DragonService.EXTRA_TIMEOUT, p.getInt("timeout", 2));
        if (Build.VERSION.SDK_INT >= 26) startForegroundService(i); else startService(i);
    }

    private void validateAndSave() {
        String h = server.getText().toString().trim();
        if (h.isEmpty()) throw new IllegalArgumentException("Server is required");
        int p = parse(port, 1, 65535, "Port");
        int max = parse(chunkMax, 32, 1048576, "Max chunk");
        int min = parse(chunkMin, 32, max, "Min chunk");
        int bMax = parse(batchMax, 1, BATCH_LIMIT, "Batch max");
        int bMin = parse(batchMin, 1, BATCH_LIMIT, "Batch min");
        if (bMin > bMax) throw new IllegalArgumentException("Batch min must not exceed batch max");
        int rec = parse(reconnect, 0, 1000000, "Reconnect every");
        int tout = parse(timeout, 1, 120, "Timeout");

        getSharedPreferences(PREFS, MODE_PRIVATE).edit()
                .putString("server", h)
                .putInt("port", p)
                .putString("token", token.getText().toString())
                .putString("wire", wireMode)
                .putInt("max", max)
                .putInt("min", min)
                .putInt("batchMax", bMax)
                .putInt("batchMin", bMin)
                .putInt("reconnect", rec)
                .putInt("timeout", tout)
                .apply();
    }

    private int parse(EditText field, int min, int max, String name) {
        int v;
        try { v = Integer.parseInt(field.getText().toString().trim()); }
        catch (Exception e) { throw new IllegalArgumentException(name + " is invalid"); }
        if (v < min || v > max) throw new IllegalArgumentException(name + " must be " + min + "-" + max);
        return v;
    }

    private void loadSettings() {
        SharedPreferences p = getSharedPreferences(PREFS, MODE_PRIVATE);
        server.setText(p.getString("server", ""));
        port.setText(Integer.toString(p.getInt("port", 53)));
        token.setText(p.getString("token", ""));
        setWireMode(p.getString("wire", "auto"));
        chunkMax.setText(Integer.toString(p.getInt("max", 1048576)));
        chunkMin.setText(Integer.toString(p.getInt("min", 32)));
        batchMax.setText(Integer.toString(p.getInt("batchMax", 1)));
        batchMin.setText(Integer.toString(p.getInt("batchMin", 1)));
        reconnect.setText(Integer.toString(p.getInt("reconnect", 1)));
        timeout.setText(Integer.toString(p.getInt("timeout", 2)));
    }

    private void updateConnectionUi(boolean active, String rawStatus) {
        String status = rawStatus == null ? (active ? "CONNECTED" : "DISCONNECTED") : rawStatus.toUpperCase();
        boolean stopping = status.contains("STOPPING");
        boolean connected = status.contains("CONNECTED") && !status.contains("DISCONNECTED");
        boolean starting = active && !connected && !stopping;

        statusChip.setText(connected ? "CONNECTED" : starting ? "CONNECTING" : stopping ? "STOPPING" : "DISCONNECTED");
        statusChip.setTextColor(connected ? OK : starting || stopping ? WARN : MUTED);
        statusChip.setBackground(roundRect(
                connected ? Color.rgb(20, 54, 42) : starting || stopping ? Color.rgb(58, 43, 22) : CARD,
                999,
                connected ? Color.rgb(46, 105, 80) : starting || stopping ? Color.rgb(112, 78, 35) : BORDER,
                1
        ));

        boolean canConnect = !active && !connectPending && !stopping;
        boolean canStop = active && !stopping;
        connectButton.setEnabled(canConnect);
        stopButton.setEnabled(canStop);

        connectButton.setTextColor(canConnect ? Color.WHITE : Color.rgb(132, 142, 155));
        connectButton.setBackground(roundRect(canConnect ? ACCENT : DISABLED, 14, canConnect ? ACCENT : BORDER, 1));
        stopButton.setTextColor(canStop ? Color.WHITE : Color.rgb(112, 121, 133));
        stopButton.setBackground(roundRect(canStop ? STOP : Color.rgb(32, 38, 47), 14, canStop ? STOP : BORDER, 1));
    }

    private int dp(int value) {
        return Math.round(value * getResources().getDisplayMetrics().density);
    }
}
