package com.secureline.protect;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.net.Uri;
import android.text.InputType;
import android.os.Bundle;
import android.os.IBinder;
import android.os.ServiceManager;
import android.view.View;
import android.view.LayoutInflater;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.Toast;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.LinearLayout;

import com.android.secureline.ISecureLineManager;
import com.secureline.ui.SecureLineDashboardTile;
import com.secureline.ui.SecureLineHeroView;
import com.secureline.ui.SecureLineProtectionRow;
import com.secureline.ui.SecureLineStatusBadge;
import com.secureline.ui.SecureLineTimelineView;

public final class MainActivity extends Activity {
    private static final int[] BRUTE_FORCE_VALUES = {8, 10, 14, 16};
    private static final long[] INACTIVITY_VALUES = {86_400_000L, 172_800_000L, 345_600_000L, 604_800_000L, 1_209_600_000L};
    private static final long[] BFU_TIMER_VALUES = {3_600_000L, 7_200_000L, 14_400_000L, 21_600_000L};
    private static final long[] CONNECTIVITY_VALUES = {900_000L, 1_800_000L};
    private static final int[] TAMPER_VALUES = {1, 2};

    private ISecureLineManager service;
    private SecureLineDashboardTile supportIdRow;
    private SecureLineDashboardTile wipeTokenRow;
    private SecureLineStatusBadge connectivityValueButton;
    private SecureLineStatusBadge tamperValueButton;
    private FrameLayout sectionContent;
    private View protectPage;
    private View licensePage;
    private View infoPage;
    private View bottomNavigation;
    private int currentSection = -1;
    private static final int SECTION_PROTECT = 0;
    private static final int SECTION_LICENSE = 1;
    private static final int SECTION_INFO = 2;

    @Override
    protected void onCreate(Bundle state) {
        super.onCreate(state);
        setContentView(R.layout.activity_shell);
        sectionContent = findViewById(R.id.section_content);
        bottomNavigation = findViewById(R.id.bottom_navigation_root);
        inflateSections();
        bindViews();
        connectService();
        bindNavigation();
        bindProtectionActions();
        bindInlineActions();
        bindInlineConfigurationButtons();
        bindLicensePage();
        bindInfoPage();
        int[] protectionRows = {
                R.id.row_remote, R.id.row_usb, R.id.row_panic, R.id.row_call,
                R.id.row_message, R.id.row_camera, R.id.row_microphone,
                R.id.row_connectivity, R.id.row_bruteforce, R.id.row_bfu,
                R.id.row_inactivity, R.id.row_secure_reboot, R.id.row_tamper
        };
        for (int rowId : protectionRows) {
            ((SecureLineProtectionRow) protectPage.findViewById(rowId)).setIconVisualSize(68, 7);
        }
        int initialSection = state == null
                ? SECTION_PROTECT
                : state.getInt("selected_section", SECTION_PROTECT);
        showSection(initialSection, false);
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (currentSection == SECTION_PROTECT) loadOverview();
        else if (currentSection == SECTION_LICENSE) loadLicensePage();
    }

    private void inflateSections() {
        LayoutInflater inflater = LayoutInflater.from(this);
        protectPage = inflater.inflate(R.layout.activity_main, sectionContent, false);
        licensePage = inflater.inflate(R.layout.activity_license, sectionContent, false);
        infoPage = inflater.inflate(R.layout.activity_wipe_information, sectionContent, false);
        protectPage.setVisibility(View.VISIBLE);
        licensePage.setVisibility(View.GONE);
        infoPage.setVisibility(View.GONE);
        sectionContent.addView(protectPage);
        sectionContent.addView(licensePage);
        sectionContent.addView(infoPage);
    }


    private void bindViews() {
        supportIdRow = protectPage.findViewById(R.id.support_id_row);
        wipeTokenRow = protectPage.findViewById(R.id.wipe_token_row);
    }

    private void connectService() {
        try {
            IBinder binder = ServiceManager.waitForService("secureline");
            service = ISecureLineManager.Stub.asInterface(binder);
        } catch (Throwable error) {
            service = null;
        }
    }

    private void bindNavigation() {
        protectPage.findViewById(R.id.wipe_information_card)
                .setOnClickListener(v -> showSection(SECTION_INFO, true));
        bottomNavigation.findViewById(R.id.nav_protect)
                .setOnClickListener(v -> showSection(SECTION_PROTECT, true));
        bottomNavigation.findViewById(R.id.nav_license)
                .setOnClickListener(v -> showSection(SECTION_LICENSE, true));
        bottomNavigation.findViewById(R.id.nav_info)
                .setOnClickListener(v -> showSection(SECTION_INFO, true));
    }

    private void showSection(int section, boolean animate) {
        if (section < SECTION_PROTECT || section > SECTION_INFO) section = SECTION_PROTECT;
        final View next = section == SECTION_PROTECT ? protectPage
                : section == SECTION_LICENSE ? licensePage : infoPage;
        final View current = currentSection == SECTION_PROTECT ? protectPage
                : currentSection == SECTION_LICENSE ? licensePage
                : currentSection == SECTION_INFO ? infoPage : null;

        protectPage.animate().cancel();
        licensePage.animate().cancel();
        infoPage.animate().cancel();

        if (!animate || current == null || current == next) {
            protectPage.setVisibility(section == SECTION_PROTECT ? View.VISIBLE : View.GONE);
            licensePage.setVisibility(section == SECTION_LICENSE ? View.VISIBLE : View.GONE);
            infoPage.setVisibility(section == SECTION_INFO ? View.VISIBLE : View.GONE);
            next.setAlpha(1f);
            next.setTranslationY(0f);
        } else {
            next.setVisibility(View.VISIBLE);
            next.setAlpha(0f);
            next.setTranslationY(dp(8));
            current.animate()
                    .alpha(0f)
                    .translationY(-dp(4))
                    .setDuration(110)
                    .withEndAction(() -> {
                        current.setVisibility(View.GONE);
                        current.setAlpha(1f);
                        current.setTranslationY(0f);
                    })
                    .start();
            next.animate()
                    .alpha(1f)
                    .translationY(0f)
                    .setStartDelay(35)
                    .setDuration(170)
                    .start();
        }

        currentSection = section;
        updateNavigationState(section);
        if (section == SECTION_PROTECT) loadOverview();
        else if (section == SECTION_LICENSE) loadLicensePage();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        outState.putInt("selected_section", currentSection);
        super.onSaveInstanceState(outState);
    }

    private void updateNavigationState(int section) {
        setNavState(R.id.nav_protect_icon, R.id.nav_protect_label, section == SECTION_PROTECT);
        setNavState(R.id.nav_license_icon, R.id.nav_license_label, section == SECTION_LICENSE);
        setNavState(R.id.nav_info_icon, R.id.nav_info_label, section == SECTION_INFO);
    }

    private void setNavState(int iconId, int labelId, boolean active) {
        int color = getColor(active ? R.color.sl_color_blue_light : R.color.sl_color_text_secondary);
        ImageView icon = bottomNavigation.findViewById(iconId);
        TextView label = bottomNavigation.findViewById(labelId);
        icon.setColorFilter(color);
        label.setTextColor(color);
        icon.animate().scaleX(active ? 1.10f : 1f).scaleY(active ? 1.10f : 1f).setDuration(140).start();
        label.animate().alpha(active ? 1f : .72f).setDuration(140).start();
    }

    private void bindProtectionActions() {
        bindReadMore(R.id.row_remote, ProtectionDetailsActivity.KEY_REMOTE);
        bindToggle(R.id.row_usb, ProtectionDetailsActivity.KEY_USB, true, () -> service.isUsbProtectionEnabled(), value -> service.setUsbProtectionEnabled(value));
        bindToggle(R.id.row_panic, ProtectionDetailsActivity.KEY_PANIC, true, () -> service.isPanicProtectionEnabled(), value -> service.setPanicProtectionEnabled(value));
        bindToggle(R.id.row_call, ProtectionDetailsActivity.KEY_CALL, false, () -> service.isCallUnlockProtectionEnabled(), value -> service.setCallUnlockProtectionEnabled(value));
        bindToggle(R.id.row_message, ProtectionDetailsActivity.KEY_MESSAGE, true, () -> service.isMessageWipeProtectionEnabled(), value -> service.setMessageWipeProtectionEnabled(value));
        bindToggle(R.id.row_camera, ProtectionDetailsActivity.KEY_CAMERA, false, () -> service.isCameraLockdownEnabled(), value -> service.setCameraLockdownEnabled(value));
        bindToggle(R.id.row_microphone, ProtectionDetailsActivity.KEY_MICROPHONE, false, () -> service.isMicrophoneLockdownEnabled(), value -> service.setMicrophoneLockdownEnabled(value));
        bindToggle(R.id.row_connectivity, ProtectionDetailsActivity.KEY_CONNECTIVITY, false, () -> service.isConnectivitySabotageProtectionEnabled(), value -> service.setConnectivitySabotageProtectionEnabled(value));
        bindValue(R.id.row_bruteforce, ProtectionDetailsActivity.KEY_BRUTEFORCE, R.array.bruteforce_labels, true, position -> service.setBruteForceLimit(BRUTE_FORCE_VALUES[position]));
        bindToggle(R.id.row_bfu, ProtectionDetailsActivity.KEY_BFU, false, () -> service.isBfuProtectionEnabled(), value -> service.setBfuProtectionEnabled(value));
        bindValue(R.id.row_inactivity, ProtectionDetailsActivity.KEY_INACTIVITY, R.array.inactivity_labels, true, position -> service.setInactivityTimeoutMs(INACTIVITY_VALUES[position]));
        bindValue(R.id.row_secure_reboot, ProtectionDetailsActivity.KEY_SECURE_REBOOT, R.array.bfu_timer_labels, false, position -> service.setBfuTimerTimeoutMs(BFU_TIMER_VALUES[position]));
        bindToggle(R.id.row_tamper, ProtectionDetailsActivity.KEY_TAMPER, false, () -> service.isTamperLockEnabled(), value -> service.setTamperLockEnabled(value));
        ((SecureLineProtectionRow) protectPage.findViewById(R.id.row_remote)).setStatusClickable(false);
    }

    private void bindReadMore(int viewId, String key) {
        ((SecureLineProtectionRow) findViewById(viewId)).setOnReadMoreClickListener(v -> openDetails(key));
    }

    private void bindToggle(int viewId, String key, boolean destructive, BoolGetter getter, BoolSetter setter) {
        SecureLineProtectionRow row = findViewById(viewId);
        row.useSwitchControl();
        bindReadMore(viewId, key);
        row.setOnStatusClickListener(v -> {
            if (!canChangeSettings()) return;
            try {
                boolean next = !getter.get();
                confirmToggleChange(key, next, destructive, () -> setter.set(next));
            } catch (Exception error) {
                showServiceUnavailable();
            }
        });
    }

    private void bindValue(int viewId, String key, int labelsRes, boolean destructive, PositionSetter setter) {
        SecureLineProtectionRow row = findViewById(viewId);
        bindReadMore(viewId, key);
        row.setOnStatusClickListener(v -> {
            if (!canChangeSettings()) return;
            CharSequence[] labels = getResources().getTextArray(labelsRes);
            new AlertDialog.Builder(this, R.style.SecureLineUI_Dialog)
                    .setTitle(R.string.current_configuration)
                    .setItems(labels, (dialog, position) -> {
                        RemoteAction action = () -> setter.set(position);
                        confirmValueChange(key, labels[position], destructive, action);
                    })
                    .show();
        });
    }

    private boolean canChangeSettings() {
        if (service == null) {
            showServiceUnavailable();
            return false;
        }
        return true;
    }


    private void confirmToggleChange(String key, boolean enabling, boolean destructive, RemoteAction action) {
        final String protectionName = getString(titleForKey(key));
        final CharSequence dialogTitle;
        final String message;

        if (enabling && destructive) {
            dialogTitle = getString(R.string.security_warning);
            message = getString(R.string.wipe_enable_confirmation_message, protectionName);
        } else if (enabling) {
            dialogTitle = getString(R.string.turn_on_named_protection, protectionName);
            message = getString(R.string.enable_confirmation_message, protectionName);
        } else {
            dialogTitle = getString(R.string.turn_off_named_protection, protectionName);
            message = getString(R.string.disable_confirmation_message, protectionName);
        }

        new AlertDialog.Builder(this, R.style.SecureLineUI_Dialog)
                .setTitle(dialogTitle)
                .setMessage(message)
                .setPositiveButton(enabling ? R.string.turn_on : R.string.turn_off,
                        (dialog, which) -> runAction(action))
                .setNegativeButton(R.string.cancel, (dialog, which) -> loadOverview())
                .setOnCancelListener(dialog -> loadOverview())
                .show();
    }

    private void confirmValueChange(
            String key,
            CharSequence newValue,
            boolean destructive,
            RemoteAction action
    ) {
        final String protectionName = getString(titleForKey(key));
        final CharSequence title = destructive
                ? getString(R.string.security_warning)
                : getString(R.string.change_named_protection, protectionName);
        final String message = destructive
                ? getString(R.string.wipe_value_change_confirmation_message,
                        protectionName, newValue)
                : getString(R.string.value_change_confirmation_message,
                        protectionName, newValue);

        new AlertDialog.Builder(this, R.style.SecureLineUI_Dialog)
                .setTitle(title)
                .setMessage(message)
                .setPositiveButton(R.string.change, (dialog, which) -> runAction(action))
                .setNegativeButton(R.string.cancel, (dialog, which) -> loadOverview())
                .setOnCancelListener(dialog -> loadOverview())
                .show();
    }

    private void confirmSecretCodeChange(String key, RemoteAction action) {
        final String protectionName = getString(titleForKey(key));
        new AlertDialog.Builder(this, R.style.SecureLineUI_Dialog)
                .setTitle(R.string.security_warning)
                .setMessage(getString(R.string.secret_code_change_confirmation_message,
                        protectionName))
                .setPositiveButton(R.string.save, (dialog, which) -> runAction(action))
                .setNegativeButton(R.string.cancel, (dialog, which) -> loadOverview())
                .setOnCancelListener(dialog -> loadOverview())
                .show();
    }

    private void runAction(RemoteAction action) {
        try {
            action.run();
            loadOverview();
        } catch (Exception error) {
            showServiceUnavailable();
        }
    }

    private int titleForKey(String key) {
        switch (key) {
            case ProtectionDetailsActivity.KEY_REMOTE: return R.string.remote_wipe;
            case ProtectionDetailsActivity.KEY_USB: return R.string.usb_protection;
            case ProtectionDetailsActivity.KEY_PANIC: return R.string.panic_protection;
            case ProtectionDetailsActivity.KEY_CALL: return R.string.call_unlock_protection;
            case ProtectionDetailsActivity.KEY_MESSAGE: return R.string.message_wipe_protection;
            case ProtectionDetailsActivity.KEY_CAMERA: return R.string.camera_lockdown_protection;
            case ProtectionDetailsActivity.KEY_MICROPHONE: return R.string.microphone_lockdown_protection;
            case ProtectionDetailsActivity.KEY_CONNECTIVITY: return R.string.connectivity_protection;
            case ProtectionDetailsActivity.KEY_BRUTEFORCE: return R.string.bruteforce_protection;
            case ProtectionDetailsActivity.KEY_BFU: return R.string.bruteforce_reboot_protection;
            case ProtectionDetailsActivity.KEY_INACTIVITY: return R.string.inactivity_protection;
            case ProtectionDetailsActivity.KEY_SECURE_REBOOT: return R.string.secure_reboot_protection;
            case ProtectionDetailsActivity.KEY_TAMPER: return R.string.device_tamper_protection;
            default: return R.string.protection_details;
        }
    }

    private void openDetails(String key) {
        Intent intent = new Intent(this, ProtectionDetailsActivity.class);
        intent.putExtra(ProtectionDetailsActivity.EXTRA_PROTECTION_KEY, key);
        startActivity(intent);
    }

    private void loadOverview() {
        if (service == null) connectService();
        if (service == null) {
            updateStatusInteractivity();
            return;
        }
        try {
            supportIdRow.setValue(safeValue(service.getSupportId()));
            wipeTokenRow.setValue(safeValue(service.getWipeToken()));

            setStatus(R.id.row_remote, getString(R.string.always_active), SecureLineStatusBadge.TYPE_ACTIVE);
            setEnabled(R.id.row_usb, service.isUsbProtectionEnabled());
            setEnabled(R.id.row_panic, service.isPanicProtectionEnabled());
            setEnabled(R.id.row_call, service.isCallUnlockProtectionEnabled());
            setEnabled(R.id.row_message, service.isMessageWipeProtectionEnabled());
            setEnabled(R.id.row_camera, service.isCameraLockdownEnabled());
            setEnabled(R.id.row_microphone, service.isMicrophoneLockdownEnabled());
            setEnabled(R.id.row_connectivity, service.isConnectivitySabotageProtectionEnabled());
            setConfigured(R.id.row_bruteforce, labelForInt(R.array.bruteforce_labels, BRUTE_FORCE_VALUES, service.getBruteForceLimit()));
            setEnabled(R.id.row_bfu, service.isBfuProtectionEnabled());
            setConfigured(R.id.row_inactivity, labelForLong(R.array.inactivity_labels, INACTIVITY_VALUES, service.getInactivityTimeoutMs()));
            setConfigured(R.id.row_secure_reboot, labelForLong(R.array.bfu_timer_labels, BFU_TIMER_VALUES, service.getBfuTimerTimeoutMs()));
            setEnabled(R.id.row_tamper, service.isTamperLockEnabled());
            updateInlineConfigurationButtons();
            updateStatusInteractivity();
        } catch (Exception error) {
            updateStatusInteractivity();
        }
    }

    private void updateStatusInteractivity() {
        int[] configurableRows = {
                R.id.row_usb, R.id.row_panic, R.id.row_call, R.id.row_message,
                R.id.row_camera, R.id.row_microphone, R.id.row_connectivity,
                R.id.row_bruteforce, R.id.row_bfu, R.id.row_inactivity,
                R.id.row_secure_reboot, R.id.row_tamper
        };
        for (int id : configurableRows) {
            ((SecureLineProtectionRow) findViewById(id)).setStatusClickable(service != null);
        }
        ((SecureLineProtectionRow) protectPage.findViewById(R.id.row_remote)).setStatusClickable(false);
    }

    private String safeValue(String value) {
        return value == null || value.trim().isEmpty() ? getString(R.string.unknown) : value;
    }

    private void setEnabled(int id, boolean enabled) {
        ((SecureLineProtectionRow) findViewById(id)).setProtectionEnabled(enabled);
    }

    private void setConfigured(int id, CharSequence text) {
        setStatus(id, text, SecureLineStatusBadge.TYPE_ACTIVE);
    }

    private void setStatus(int id, CharSequence text, int type) {
        ((SecureLineProtectionRow) findViewById(id)).setStatus(text, type);
    }

    private CharSequence labelForInt(int resource, int[] values, int current) {
        CharSequence[] labels = getResources().getTextArray(resource);
        for (int i = 0; i < values.length && i < labels.length; i++) {
            if (values[i] == current) return labels[i];
        }
        return getString(R.string.unknown);
    }

    private CharSequence labelForLong(int resource, long[] values, long current) {
        CharSequence[] labels = getResources().getTextArray(resource);
        for (int i = 0; i < values.length && i < labels.length; i++) {
            if (values[i] == current) return labels[i];
        }
        return getString(R.string.unknown);
    }

    private void bindInlineActions() {
        SecureLineProtectionRow remote = protectPage.findViewById(R.id.row_remote);
        TextView link = new TextView(this);
        link.setText(R.string.remote_wipe_portal);
        link.setTextColor(getColor(R.color.sl_color_blue_soft));
        link.setTextSize(13);
        link.setOnClickListener(v -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.remote_wipe_portal_url)))));
        remote.getInlineContainer().addView(link);

        SecureLineProtectionRow message = protectPage.findViewById(R.id.row_message);
        EditText input = new EditText(this);
        input.setHint(R.string.message_wipe_secret_hint);
        input.setSingleLine(true);
        input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
        input.setTextColor(getColor(R.color.sl_color_text_primary));
        input.setHintTextColor(getColor(R.color.sl_color_text_muted));
        message.getInlineContainer().addView(input, new LinearLayout.LayoutParams(-1,-2));
        Button save = new Button(this);
        save.setText(R.string.message_wipe_save);
        save.setBackgroundResource(R.drawable.sl_button_primary);
        LinearLayout.LayoutParams bp = new LinearLayout.LayoutParams(-1, dp(48)); bp.topMargin=dp(8);
        message.getInlineContainer().addView(save,bp);
        save.setOnClickListener(v -> {
            String secret=input.getText().toString().trim();
            if(secret.length()<12){Toast.makeText(this,R.string.message_wipe_secret_too_short,Toast.LENGTH_SHORT).show();return;}
            if(secret.matches(".*\\s+.*")){Toast.makeText(this,R.string.message_wipe_secret_no_spaces,Toast.LENGTH_SHORT).show();return;}
            if(service==null){showServiceUnavailable();return;}
            confirmSecretCodeChange(ProtectionDetailsActivity.KEY_MESSAGE,()->{service.setMessageWipeSecret(secret); input.setText(""); Toast.makeText(this,R.string.message_wipe_secret_saved,Toast.LENGTH_SHORT).show();});
        });
    }


    private void bindInlineConfigurationButtons() {
        connectivityValueButton = createInlineValueButton(
                ProtectionDetailsActivity.KEY_CONNECTIVITY,
                R.array.connectivity_labels,
                false,
                position -> service.setConnectivitySabotageTimeoutMs(CONNECTIVITY_VALUES[position]));
        ((SecureLineProtectionRow) protectPage.findViewById(R.id.row_connectivity))
                .getInlineContainer().addView(connectivityValueButton, inlineValueButtonParams());

        tamperValueButton = createInlineValueButton(
                ProtectionDetailsActivity.KEY_TAMPER,
                R.array.tamper_labels,
                false,
                position -> service.setTamperSensitivity(TAMPER_VALUES[position]));
        ((SecureLineProtectionRow) protectPage.findViewById(R.id.row_tamper))
                .getInlineContainer().addView(tamperValueButton, inlineValueButtonParams());
    }

    private SecureLineStatusBadge createInlineValueButton(
            String key,
            int labelsRes,
            boolean destructive,
            PositionSetter setter
    ) {
        SecureLineStatusBadge button = new SecureLineStatusBadge(this);
        button.setClickable(true);
        button.setFocusable(true);
        button.setOnClickListener(v -> {
            if (service == null || !button.isEnabled()) return;
            CharSequence[] labels = getResources().getTextArray(labelsRes);
            new AlertDialog.Builder(this, R.style.SecureLineUI_Dialog)
                    .setTitle(R.string.current_configuration)
                    .setItems(labels, (dialog, position) ->
                            confirmValueChange(key, labels[position], destructive,
                                    () -> setter.set(position)))
                    .show();
        });
        return button;
    }

    private LinearLayout.LayoutParams inlineValueButtonParams() {
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = dp(4);
        return params;
    }

    private void styleInlineValueButton(
            SecureLineStatusBadge button,
            boolean enabled,
            CharSequence value
    ) {
        if (button == null) return;
        button.setText(value);
        button.setEnabled(enabled);
        button.setClickable(enabled);
        button.setFocusable(enabled);
        button.setAlpha(enabled ? 1f : 0.72f);
        button.setBadgeType(enabled
                ? SecureLineStatusBadge.TYPE_ACTIVE
                : SecureLineStatusBadge.TYPE_NEUTRAL);
    }

    private int indexOf(long[] values, long current) {
        for (int i = 0; i < values.length; i++) if (values[i] == current) return i;
        return -1;
    }

    private int indexOf(int[] values, int current) {
        for (int i = 0; i < values.length; i++) if (values[i] == current) return i;
        return -1;
    }

    private void updateInlineConfigurationButtons() throws Exception {
        boolean connectivityEnabled = service.isConnectivitySabotageProtectionEnabled();
        int connectivity = indexOf(CONNECTIVITY_VALUES, service.getConnectivitySabotageTimeoutMs());
        CharSequence connectivityLabel = connectivity >= 0
                ? getResources().getTextArray(R.array.connectivity_labels)[connectivity]
                : getString(R.string.unknown);
        styleInlineValueButton(connectivityValueButton, connectivityEnabled, connectivityLabel);

        boolean tamperEnabled = service.isTamperLockEnabled();
        int tamper = indexOf(TAMPER_VALUES, service.getTamperSensitivity());
        CharSequence tamperLabel = tamper >= 0
                ? getResources().getTextArray(R.array.tamper_labels)[tamper]
                : getString(R.string.unknown);
        styleInlineValueButton(tamperValueButton, tamperEnabled, tamperLabel);
    }

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

    private void showServiceUnavailable() {
        Toast.makeText(this, R.string.service_unavailable, Toast.LENGTH_SHORT).show();
    }

    private void bindLicensePage() {
        SecureLineHeroView licenseHero = licensePage.findViewById(R.id.license_hero);
        licenseHero.setUtilityIconMode(124, 1, 12);
        SecureLineTimelineView timeline = licensePage.findViewById(R.id.license_timeline);
        timeline.setSteps(
                new CharSequence[]{getText(R.string.license_step_1_title), getText(R.string.license_step_2_title), getText(R.string.license_step_3_title), getText(R.string.license_step_4_title)},
                new CharSequence[]{getText(R.string.license_step_1_description), getText(R.string.license_step_2_description), getText(R.string.license_step_3_description), getText(R.string.license_step_4_description)});
        licensePage.findViewById(R.id.renew_license_button).setOnClickListener(v ->
                startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.renew_license_url)))));
    }

    private void loadLicensePage() {
        try {
            if (service == null) connectService();
            if (service == null) throw new IllegalStateException("Lemenzo service unavailable");
            boolean active = service.isLicenseActive();
            SecureLineDashboardTile status = licensePage.findViewById(R.id.license_status);
            status.setValue(getString(active ? R.string.active : R.string.expired));
            status.setValueColor(getColor(active ? R.color.sl_color_text_primary : R.color.sl_color_danger));
            int days = Math.max(0, service.getLicenseDaysLeft());
            ((SecureLineDashboardTile) licensePage.findViewById(R.id.license_days_left))
                    .setValue(getString(days == 1 ? R.string.day_singular : R.string.days_plural, days));
            String plan = service.getLicensePlan();
            ((SecureLineDashboardTile) licensePage.findViewById(R.id.license_plan))
                    .setValue(plan == null || plan.trim().isEmpty() ? getString(R.string.unknown) : plan);
        } catch (Exception error) {
            SecureLineDashboardTile status = licensePage.findViewById(R.id.license_status);
            status.setValue(getString(R.string.license_service_unavailable));
            status.setValueColor(getColor(R.color.sl_color_danger));
        }
    }

    private void bindInfoPage() {
        SecureLineHeroView infoHero = infoPage.findViewById(R.id.info_hero);
        infoHero.setUtilityIconMode(124, 1, 12);
        infoPage.findViewById(R.id.privacy_policy_button).setOnClickListener(v -> {
            try {
                startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://lemenzo.eu/privacy")));
            } catch (Exception error) {
                Toast.makeText(this, R.string.privacy_link_failed, Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    public void onBackPressed() {
        if (currentSection != SECTION_PROTECT) {
            showSection(SECTION_PROTECT, true);
            return;
        }
        super.onBackPressed();
    }

    private interface BoolGetter { boolean get() throws Exception; }
    private interface BoolSetter { void set(boolean value) throws Exception; }
    private interface PositionSetter { void set(int position) throws Exception; }
    private interface RemoteAction { void run() throws Exception; }
}
