/*
 * <!--This file is part of LSPosed.
 *
 * LSPosed is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * LSPosed is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with LSPosed.  If not, see <https://www.gnu.org/licenses/>.
 *
 * Copyright (C) 2021 LSPosed Contributors-->
 */

package org.lsposed.manager.ui.fragment;

import android.app.Activity;
import android.app.Dialog;
import android.os.Build;
import android.os.Bundle;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.text.HtmlCompat;
import androidx.core.view.MenuProvider;
import androidx.fragment.app.DialogFragment;

import org.lsposed.lspd.ILSPManagerService;
import org.lsposed.manager.BuildConfig;
import org.lsposed.manager.ConfigManager;
import org.lsposed.manager.R;
import org.lsposed.manager.databinding.DialogAboutBinding;
import org.lsposed.manager.databinding.FragmentHomeBinding;
import org.lsposed.manager.ui.dialog.BlurBehindDialogBuilder;
import org.lsposed.manager.ui.dialog.FlashDialogBuilder;
import org.lsposed.manager.ui.dialog.WelcomeDialog;
import org.lsposed.manager.util.NavUtil;
import org.lsposed.manager.util.UpdateUtil;
import org.lsposed.manager.util.chrome.LinkTransformationMethod;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicBoolean;

import rikka.core.util.ClipboardUtils;
import rikka.material.app.LocaleDelegate;

public class HomeFragment extends BaseFragment implements MenuProvider {
    private FragmentHomeBinding binding;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        WelcomeDialog.showIfNeed(getChildFragmentManager());
    }

    @Override
    public void onPrepareMenu(Menu menu) {
        menu.findItem(R.id.menu_about).setOnMenuItemClickListener(v -> {
            showAbout();
            return true;
        });
        menu.findItem(R.id.menu_issue).setOnMenuItemClickListener(v -> {
            NavUtil.startURL(requireActivity(), "https://github.com/JingMatrix/LSPosed/issues/new/choose");
            return true;
        });
    }

    @Override
    public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) {

    }

    @Override
    public boolean onMenuItemSelected(@NonNull MenuItem menuItem) {
        return false;
    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        binding = FragmentHomeBinding.inflate(inflater, container, false);
        setupToolbar(binding.toolbar, binding.clickView, R.string.app_name, R.menu.menu_home);
        binding.toolbar.setNavigationIcon(null);
        binding.toolbar.setOnClickListener(v -> showAbout());
        binding.clickView.setOnClickListener(v -> showAbout());
        binding.appBar.setLiftable(true);
        binding.nestedScrollView.getBorderViewDelegate().setBorderVisibilityChangedListener((top, oldTop, bottom, oldBottom) -> binding.appBar.setLifted(!top));

        updateStates(requireActivity(), ConfigManager.isBinderAlive(), UpdateUtil.needUpdate());

        return binding.getRoot();
    }

    private void updateStates(Activity activity, boolean binderAlive, boolean needUpdate) {
        if (binderAlive) {
            if (needUpdate) {
                binding.updateTitle.setText(R.string.need_update);
                binding.updateSummary.setText(getString(R.string.please_update_summary));
                binding.statusIcon.setImageResource(R.drawable.ic_round_update_24);
                binding.updateBtn.setOnClickListener(v -> {
                    if (UpdateUtil.canInstall()) {
                        new FlashDialogBuilder(activity, null).show();
                    } else {
                        NavUtil.startURL(activity, getString(R.string.latest_url));
                    }
                });
                binding.updateCard.setVisibility(View.VISIBLE);
            } else {
                binding.updateCard.setVisibility(View.GONE);
            }
            boolean dex2oatAbnormal = ConfigManager.getDex2OatWrapperCompatibility() != ILSPManagerService.DEX2OAT_OK && !ConfigManager.dex2oatFlagsLoaded();
            var sepolicyAbnormal = !ConfigManager.isSepolicyLoaded();
            var systemServerAbnormal = !ConfigManager.systemServerRequested();
            if (sepolicyAbnormal || systemServerAbnormal || dex2oatAbnormal) {
                binding.statusTitle.setText(R.string.partial_activated);
                binding.statusIcon.setImageResource(R.drawable.ic_round_warning_24);
                binding.warningCard.setVisibility(View.VISIBLE);
                if (sepolicyAbnormal) {
                    binding.warningTitle.setText(R.string.selinux_policy_not_loaded_summary);
                    binding.warningSummary.setText(HtmlCompat.fromHtml(getString(R.string.selinux_policy_not_loaded), HtmlCompat.FROM_HTML_MODE_LEGACY));
                }
                if (systemServerAbnormal) {
                    binding.warningTitle.setText(R.string.system_inject_fail_summary);
                    binding.warningSummary.setText(HtmlCompat.fromHtml(getString(R.string.system_inject_fail), HtmlCompat.FROM_HTML_MODE_LEGACY));
                }
                if (dex2oatAbnormal) {
                    binding.warningTitle.setText(R.string.system_prop_incorrect_summary);
                    binding.warningSummary.setText(HtmlCompat.fromHtml(getString(R.string.system_prop_incorrect), HtmlCompat.FROM_HTML_MODE_LEGACY));
                }
            } else {
                binding.warningCard.setVisibility(View.GONE);
                binding.statusTitle.setText(R.string.activated);
                binding.statusIcon.setImageResource(R.drawable.ic_round_check_circle_24);
            }
            binding.statusSummary.setText(String.format(LocaleDelegate.getDefaultLocale(), "%s (%d) - %s",
                    ConfigManager.getXposedVersionName(), ConfigManager.getXposedVersionCode(), ConfigManager.getApi()));
            binding.developerWarningCard.setVisibility(isDeveloper() ? View.VISIBLE : View.GONE);
        } else {
            boolean isMagiskInstalled = ConfigManager.isMagiskInstalled();
            if (isMagiskInstalled) {
                binding.updateTitle.setText(R.string.install);
                binding.updateSummary.setText(R.string.install_summary);
                binding.statusIcon.setImageResource(R.drawable.ic_round_error_outline_24);
                binding.updateBtn.setOnClickListener(v -> {
                    if (UpdateUtil.canInstall()) {
                        new FlashDialogBuilder(activity, null).show();
                    } else {
                        NavUtil.startURL(activity, getString(R.string.install_url));
                    }
                });
                binding.updateCard.setVisibility(View.VISIBLE);
            } else {
                binding.updateCard.setVisibility(View.GONE);
            }
            binding.warningCard.setVisibility(View.GONE);
            binding.statusTitle.setText(R.string.not_installed);
            binding.statusSummary.setText(R.string.not_install_summary);
        }

        if (ConfigManager.isBinderAlive()) {
            binding.apiVersion.setText(String.valueOf(ConfigManager.getXposedApiVersion()));
            binding.api.setText(ConfigManager.isDexObfuscateEnabled() ? R.string.enabled : R.string.not_enabled);
            binding.frameworkVersion.setText(String.format(LocaleDelegate.getDefaultLocale(), "%1$s (%2$d)", ConfigManager.getXposedVersionName(), ConfigManager.getXposedVersionCode()));
            binding.managerPackageName.setText(activity.getPackageName());
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
                binding.dex2oatWrapper.setText(String.format(LocaleDelegate.getDefaultLocale(), "%s (%s)", getString(R.string.unsupported), getString(R.string.android_version_unsatisfied)));
            } else switch (ConfigManager.getDex2OatWrapperCompatibility()) {
                case ILSPManagerService.DEX2OAT_OK ->
                        binding.dex2oatWrapper.setText(R.string.supported);
                case ILSPManagerService.DEX2OAT_CRASHED ->
                        binding.dex2oatWrapper.setText(String.format(LocaleDelegate.getDefaultLocale(), "%s (%s)", getString(R.string.unsupported), getString(R.string.crashed)));
                case ILSPManagerService.DEX2OAT_MOUNT_FAILED ->
                        binding.dex2oatWrapper.setText(String.format(LocaleDelegate.getDefaultLocale(), "%s (%s)", getString(R.string.unsupported), getString(R.string.mount_failed)));
                case ILSPManagerService.DEX2OAT_SELINUX_PERMISSIVE ->
                        binding.dex2oatWrapper.setText(String.format(LocaleDelegate.getDefaultLocale(), "%s (%s)", getString(R.string.unsupported), getString(R.string.selinux_permissive)));
                case ILSPManagerService.DEX2OAT_SEPOLICY_INCORRECT ->
                        binding.dex2oatWrapper.setText(String.format(LocaleDelegate.getDefaultLocale(), "%s (%s)", getString(R.string.unsupported), getString(R.string.sepolicy_incorrect)));
            }
        } else {
            binding.apiVersion.setText(R.string.not_installed);
            binding.api.setText(R.string.not_installed);
            binding.frameworkVersion.setText(R.string.not_installed);
            binding.managerPackageName.setText(activity.getPackageName());
        }

        if (Build.VERSION.PREVIEW_SDK_INT != 0) {
            binding.systemVersion.setText(String.format(LocaleDelegate.getDefaultLocale(), "%1$s Preview (API %2$d)", Build.VERSION.CODENAME, Build.VERSION.SDK_INT));
        } else {
            binding.systemVersion.setText(String.format(LocaleDelegate.getDefaultLocale(), "%1$s (API %2$d)", Build.VERSION.RELEASE, Build.VERSION.SDK_INT));
        }

        binding.device.setText(getDevice());
        binding.systemAbi.setText(Build.SUPPORTED_ABIS[0]);
        String info = activity.getString(R.string.info_api_version) +
                "\n" +
                binding.apiVersion.getText() +
                "\n\n" +
                activity.getString(R.string.settings_xposed_api_call_protection) +
                "\n" +
                binding.api.getText() +
                "\n\n" +
                activity.getString(R.string.info_dex2oat_wrapper) +
                "\n" +
                binding.dex2oatWrapper.getText() +
                "\n\n" +
                activity.getString(R.string.info_framework_version) +
                "\n" +
                binding.frameworkVersion.getText() +
                "\n\n" +
                activity.getString(R.string.info_manager_package_name) +
                "\n" +
                binding.managerPackageName.getText() +
                "\n\n" +
                activity.getString(R.string.info_system_version) +
                "\n" +
                binding.systemVersion.getText() +
                "\n\n" +
                activity.getString(R.string.info_device) +
                "\n" +
                binding.device.getText() +
                "\n\n" +
                activity.getString(R.string.info_system_abi) +
                "\n" +
                binding.systemAbi.getText();
        var map = new HashMap<String, String>();
        map.put("apiVersion", binding.apiVersion.getText().toString());
        map.put("api", binding.api.getText().toString());
        map.put("frameworkVersion", binding.frameworkVersion.getText().toString());
        map.put("systemAbi", Arrays.toString(Build.SUPPORTED_ABIS));
        binding.copyInfo.setOnClickListener(v -> {
            ClipboardUtils.put(activity, info);
            showHint(R.string.info_copied, false);
        });
    }

    private String getDevice() {
        String manufacturer = Character.toUpperCase(Build.MANUFACTURER.charAt(0)) + Build.MANUFACTURER.substring(1);
        if (!Build.BRAND.equals(Build.MANUFACTURER)) {
            manufacturer += " " + Character.toUpperCase(Build.BRAND.charAt(0)) + Build.BRAND.substring(1);
        }
        manufacturer += " " + Build.MODEL + " ";
        return manufacturer;
    }

    private boolean isDeveloper() {
        var developer = new AtomicBoolean(false);
        var pids = Paths.get("/data/local/tmp/.studio/ipids");
        try (var dir = Files.list(pids)) {
            dir.findFirst().ifPresent(name -> {
                var pid = Integer.parseInt(name.getFileName().toString());
                try {
                    Os.kill(pid, 0);
                    developer.set(true);
                } catch (ErrnoException e) {
                    if (e.errno == OsConstants.ESRCH) {
                        try {
                            Files.delete(name);
                        } catch (IOException ignored) {
                        }
                    } else {
                        developer.set(true);
                    }
                }
            });
        } catch (IOException e) {
            return false;
        }
        return developer.get();
    }

    public static class AboutDialog extends DialogFragment {
        @NonNull
        @Override
        public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
            DialogAboutBinding binding = DialogAboutBinding.inflate(getLayoutInflater(), null, false);
            binding.designAboutTitle.setText(R.string.app_name);
            binding.designAboutInfo.setMovementMethod(LinkMovementMethod.getInstance());
            binding.designAboutInfo.setTransformationMethod(new LinkTransformationMethod(requireActivity()));
            binding.designAboutInfo.setText(HtmlCompat.fromHtml(getString(
                    R.string.about_view_source_code,
                    "<b><a href=\"https://github.com/JingMatrix/LSPosed\">GitHub</a></b>",
                    "<b><a href=\"https://t.me/LSPosed\">Telegram</a></b>"), HtmlCompat.FROM_HTML_MODE_LEGACY));
            binding.designAboutVersion.setText(String.format(LocaleDelegate.getDefaultLocale(), "%s (%d)", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
            return new BlurBehindDialogBuilder(requireContext())
                    .setView(binding.getRoot()).create();
        }
    }

    private void showAbout() {
        new AboutDialog().show(getChildFragmentManager(), "about");
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        binding = null;
    }
}
