package com.topjohnwu.magisk.fragments;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.hardware.fingerprint.FingerprintManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v14.preference.SwitchPreference;
import android.support.v7.app.AlertDialog;
import android.support.v7.preference.ListPreference;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceCategory;
import android.support.v7.preference.PreferenceFragmentCompat;
import android.support.v7.preference.PreferenceScreen;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.Toast;

import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.Data;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.asyncs.CheckUpdates;
import com.topjohnwu.magisk.asyncs.PatchAPK;
import com.topjohnwu.magisk.components.CustomAlertDialog;
import com.topjohnwu.magisk.receivers.DownloadReceiver;
import com.topjohnwu.magisk.utils.Download;
import com.topjohnwu.magisk.utils.FingerprintHelper;
import com.topjohnwu.magisk.utils.LocaleManager;
import com.topjohnwu.magisk.utils.RootUtils;
import com.topjohnwu.magisk.utils.Topic;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.ShellUtils;

import java.io.IOException;
import java.util.Locale;

public class SettingsFragment extends PreferenceFragmentCompat
        implements SharedPreferences.OnSharedPreferenceChangeListener,
        Topic.Subscriber, Topic.AutoSubscriber {

    private PreferenceScreen prefScreen;

    private ListPreference updateChannel, suAccess, autoRes, suNotification,
            requestTimeout, multiuserMode, namespaceMode;
    private MagiskManager mm;
    private PreferenceCategory generalCatagory;

    @Override
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        setPreferencesFromResource(R.xml.app_settings, rootKey);
        mm = Data.MM();
        prefScreen = getPreferenceScreen();

        generalCatagory = (PreferenceCategory) findPreference("general");
        PreferenceCategory magiskCategory = (PreferenceCategory) findPreference("magisk");
        PreferenceCategory suCategory = (PreferenceCategory) findPreference("superuser");
        Preference hideManager = findPreference("hide");
        Preference restoreManager = findPreference("restore");
        findPreference("clear").setOnPreferenceClickListener((pref) -> {
            mm.prefs.edit().remove(Const.Key.ETAG_KEY).apply();
            mm.repoDB.clearRepo();
            Utils.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT);
            return true;
        });

        updateChannel = (ListPreference) findPreference(Const.Key.UPDATE_CHANNEL);
        suAccess = (ListPreference) findPreference(Const.Key.ROOT_ACCESS);
        autoRes = (ListPreference) findPreference(Const.Key.SU_AUTO_RESPONSE);
        requestTimeout = (ListPreference) findPreference(Const.Key.SU_REQUEST_TIMEOUT);
        suNotification = (ListPreference) findPreference(Const.Key.SU_NOTIFICATION);
        multiuserMode = (ListPreference) findPreference(Const.Key.SU_MULTIUSER_MODE);
        namespaceMode = (ListPreference) findPreference(Const.Key.SU_MNT_NS);
        SwitchPreference reauth = (SwitchPreference) findPreference(Const.Key.SU_REAUTH);
        SwitchPreference fingerprint = (SwitchPreference) findPreference(Const.Key.SU_FINGERPRINT);

        updateChannel.setOnPreferenceChangeListener((p, o) -> {
            String prev =String.valueOf(Data.updateChannel);
            int channel = Integer.parseInt((String) o);
            if (channel == Const.Value.CUSTOM_CHANNEL) {
                View v = LayoutInflater.from(requireActivity()).inflate(R.layout.custom_channel_dialog, null);
                EditText url = v.findViewById(R.id.custom_url);
                url.setText(mm.prefs.getString(Const.Key.CUSTOM_CHANNEL, ""));
                new AlertDialog.Builder(requireActivity())
                        .setTitle(R.string.settings_update_custom)
                        .setView(v)
                        .setPositiveButton(R.string.ok, (d, i) ->
                                mm.prefs.edit().putString(Const.Key.CUSTOM_CHANNEL,
                                        url.getText().toString()).apply())
                        .setNegativeButton(R.string.close, (d, i) ->
                                mm.prefs.edit().putString(Const.Key.UPDATE_CHANNEL, prev).apply())
                        .setOnCancelListener(d ->
                                mm.prefs.edit().putString(Const.Key.UPDATE_CHANNEL, prev).apply())
                        .show();
            }
            return true;
        });

        setSummary();

        // Disable dangerous settings in secondary user
        if (Const.USER_ID > 0) {
            suCategory.removePreference(multiuserMode);
        }

        // Disable re-authentication option on Android O, it will not work
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            reauth.setEnabled(false);
            reauth.setChecked(false);
            reauth.setSummary(R.string.android_o_not_support);
        }

        // Disable fingerprint option if not possible
        if (!FingerprintHelper.canUseFingerprint()) {
            fingerprint.setEnabled(false);
            fingerprint.setChecked(false);
            fingerprint.setSummary(R.string.disable_fingerprint);
        }

        if (Data.magiskVersionCode >= Const.MAGISK_VER.MANAGER_HIDE) {
            if (mm.getPackageName().equals(Const.ORIG_PKG_NAME)) {
                hideManager.setOnPreferenceClickListener((pref) -> {
                    PatchAPK.hideManager(requireActivity());
                    return true;
                });
                generalCatagory.removePreference(restoreManager);
            } else {
                if (Download.checkNetworkStatus(mm)) {
                    restoreManager.setOnPreferenceClickListener((pref) -> {
                        Download.receive(
                            requireActivity(), new DownloadReceiver() {
                                @Override
                                public void onDownloadDone(Context context, Uri uri) {
                                    Data.exportPrefs();
                                    Shell.su("cp " + uri.getPath() + " /data/local/tmp/manager.apk").exec();
                                    if (ShellUtils.fastCmdResult("pm install /data/local/tmp/manager.apk")) {
                                        Shell.su("rm -f /data/local/tmp/manager.apk").exec();
                                        RootUtils.uninstallPkg(context.getPackageName());
                                        return;
                                    }
                                    Shell.su("rm -f /data/local/tmp/manager.apk").exec();
                                }
                            },
                            Data.managerLink,
                            Utils.fmt("MagiskManager-v%s.apk", Data.remoteManagerVersionString)
                        );
                        return true;
                    });
                } else {
                    generalCatagory.removePreference(restoreManager);
                }
                generalCatagory.removePreference(hideManager);
            }
        } else {
            generalCatagory.removePreference(restoreManager);
            generalCatagory.removePreference(hideManager);
        }

        if (!Shell.rootAccess() || (Const.USER_ID > 0 &&
                Data.multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED)) {
            prefScreen.removePreference(suCategory);
        }

        if (!Shell.rootAccess()) {
            prefScreen.removePreference(magiskCategory);
            generalCatagory.removePreference(hideManager);
        } else if (Data.magiskVersionCode < Const.MAGISK_VER.UNIFIED) {
            prefScreen.removePreference(magiskCategory);
        }
    }

    private void setLocalePreference(ListPreference lp) {
        CharSequence[] entries = new CharSequence[LocaleManager.locales.size() + 1];
        CharSequence[] entryValues = new CharSequence[LocaleManager.locales.size() + 1];
        entries[0] = LocaleManager.getString(LocaleManager.defaultLocale, R.string.system_default);
        entryValues[0] = "";
        int i = 1;
        for (Locale locale : LocaleManager.locales) {
            entries[i] = locale.getDisplayName(locale);
            entryValues[i++] = locale.toLanguageTag();
        }
        lp.setEntries(entries);
        lp.setEntryValues(entryValues);
        lp.setSummary(LocaleManager.locale.getDisplayName(LocaleManager.locale));
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        mm.prefs.registerOnSharedPreferenceChangeListener(this);
        Topic.subscribe(this);
        requireActivity().setTitle(R.string.settings);
        return super.onCreateView(inflater, container, savedInstanceState);
    }

    @Override
    public void onDestroyView() {
        mm.prefs.unregisterOnSharedPreferenceChangeListener(this);
        Topic.unsubscribe(this);
        super.onDestroyView();
    }

    @Override
    public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
        switch (key) {
            case Const.Key.ROOT_ACCESS:
            case Const.Key.SU_MULTIUSER_MODE:
            case Const.Key.SU_MNT_NS:
                mm.mDB.setSettings(key, Utils.getPrefsInt(prefs, key));
                break;
        }
        Data.loadConfig();
        setSummary();
        switch (key) {
            case Const.Key.DARK_THEME:
                Topic.publish(false, Topic.RELOAD_ACTIVITY);
                break;
            case Const.Key.COREONLY:
                if (prefs.getBoolean(key, false)) {
                    try {
                        Const.MAGISK_DISABLE_FILE.createNewFile();
                    } catch (IOException ignored) {}
                } else {
                    Const.MAGISK_DISABLE_FILE.delete();
                }
                Utils.toast(R.string.settings_reboot_toast, Toast.LENGTH_LONG);
                break;
            case Const.Key.MAGISKHIDE:
                if (prefs.getBoolean(key, false)) {
                    Shell.su("magiskhide --enable").submit();
                } else {
                    Shell.su("magiskhide --disable").submit();
                }
                break;
            case Const.Key.HOSTS:
                if (prefs.getBoolean(key, false)) {
                    Shell.su("cp -af /system/etc/hosts " + Const.MAGISK_HOST_FILE,
                            "mount -o bind " + Const.MAGISK_HOST_FILE + " /system/etc/hosts")
                            .submit();
                } else {
                    Shell.su("umount -l /system/etc/hosts",
                            "rm -f " + Const.MAGISK_HOST_FILE)
                            .submit();
                }
                break;
            case Const.Key.LOCALE:
                LocaleManager.setLocale(mm);
                Topic.publish(false, Topic.RELOAD_ACTIVITY);
                break;
            case Const.Key.UPDATE_CHANNEL:
            case Const.Key.CUSTOM_CHANNEL:
                CheckUpdates.check();
                break;
            case Const.Key.CHECK_UPDATES:
                Utils.setupUpdateCheck();
                break;
        }
    }

    @Override
    public boolean onPreferenceTreeClick(Preference preference) {
        String key = preference.getKey();
        switch (key) {
            case Const.Key.SU_FINGERPRINT:
                boolean checked = ((SwitchPreference) preference).isChecked();
                ((SwitchPreference) preference).setChecked(!checked);
                CustomAlertDialog dialog = new CustomAlertDialog(requireActivity());
                CustomAlertDialog.ViewHolder vh = dialog.getViewHolder();
                Drawable fingerprint = getResources().getDrawable(R.drawable.ic_fingerprint);
                fingerprint.setBounds(0, 0, Utils.dpInPx(50), Utils.dpInPx(50));
                Resources.Theme theme = requireActivity().getTheme();
                TypedArray ta = theme.obtainStyledAttributes(new int[] {R.attr.imageColorTint});
                fingerprint.setTint(ta.getColor(0, Color.GRAY));
                ta.recycle();
                vh.messageView.setCompoundDrawables(null, null, null, fingerprint);
                vh.messageView.setCompoundDrawablePadding(Utils.dpInPx(20));
                vh.messageView.setGravity(Gravity.CENTER);
                try {
                    FingerprintHelper helper = new FingerprintHelper() {
                        @Override
                        public void onAuthenticationError(int errorCode, CharSequence errString) {
                            vh.messageView.setTextColor(Color.RED);
                            vh.messageView.setText(errString);
                        }

                        @Override
                        public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
                            vh.messageView.setTextColor(Color.RED);
                            vh.messageView.setText(helpString);
                        }

                        @Override
                        public void onAuthenticationFailed() {
                            vh.messageView.setTextColor(Color.RED);
                            vh.messageView.setText(R.string.auth_fail);
                        }

                        @Override
                        public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
                            dialog.dismiss();
                            ((SwitchPreference) preference).setChecked(checked);
                            mm.mDB.setSettings(key, checked ? 1 : 0);

                        }
                    };
                    dialog.setMessage(R.string.auth_fingerprint)
                            .setNegativeButton(R.string.close, (d, w) -> helper.cancel())
                            .setOnCancelListener(d -> helper.cancel())
                            .show();
                    helper.authenticate();
                } catch (Exception e) {
                    e.printStackTrace();
                    Utils.toast(R.string.auth_fail, Toast.LENGTH_SHORT);
                }
                break;
        }
        return true;
    }

    private void setSummary() {
        updateChannel.setSummary(getResources()
                .getStringArray(R.array.update_channel)[Data.updateChannel]);
        suAccess.setSummary(getResources()
                .getStringArray(R.array.su_access)[Data.suAccessState]);
        autoRes.setSummary(getResources()
                .getStringArray(R.array.auto_response)[Data.suResponseType]);
        suNotification.setSummary(getResources()
                .getStringArray(R.array.su_notification)[Data.suNotificationType]);
        requestTimeout.setSummary(
                getString(R.string.request_timeout_summary,
                        mm.prefs.getString(Const.Key.SU_REQUEST_TIMEOUT, "10")));
        multiuserMode.setSummary(getResources()
                .getStringArray(R.array.multiuser_summary)[Data.multiuserMode]);
        namespaceMode.setSummary(getResources()
                .getStringArray(R.array.namespace_summary)[Data.suNamespaceMode]);
    }

    @Override
    public void onPublish(int topic, Object[] result) {
        setLocalePreference((ListPreference) findPreference(Const.Key.LOCALE));
    }

    @Override
    public int[] getSubscribedTopics() {
        return new int[] {Topic.LOCALE_FETCH_DONE};
    }
}
