/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.inputmethod;

import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
import android.hardware.input.KeyboardLayout;
import android.hardware.input.KeyboardLayoutSelectionResult;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import android.view.InputDevice;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;

import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;

import com.android.internal.util.Preconditions;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
import com.android.settings.inputmethod.InputPeripheralsSettingsUtils.KeyboardInfo;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class NewKeyboardLayoutEnabledLocalesFragment extends DashboardFragment
        implements InputManager.InputDeviceListener {

    private static final String TAG = "NewKeyboardLayoutEnabledLocalesFragment";

    private InputManager mIm;
    private InputMethodManager mImm;
    private InputDeviceIdentifier mInputDeviceIdentifier;
    private int mUserId;
    private int mInputDeviceId;
    private Context mContext;
    private ArrayList<KeyboardInfo> mKeyboardInfoList = new ArrayList<>();

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        mContext = context;
        final int profileType = getArguments().getInt(ProfileSelectFragment.EXTRA_PROFILE);
        final int currentUserId = UserHandle.myUserId();
        final int newUserId;
        final UserManager userManager = mContext.getSystemService(UserManager.class);

        switch (profileType) {
            case ProfileSelectFragment.ProfileType.WORK: {
                // If the user is a managed profile user, use currentUserId directly. Or get the
                // managed profile userId instead.
                newUserId = userManager.isManagedProfile()
                        ? currentUserId : Utils.getManagedProfileId(userManager, currentUserId);
                break;
            }
            case ProfileSelectFragment.ProfileType.PRIVATE: {
                // If the user is a private profile user, use currentUserId directly. Or get the
                // private profile userId instead.
                newUserId = userManager.isPrivateProfile()
                        ? currentUserId
                        : Utils.getCurrentUserIdOfType(
                                userManager, ProfileSelectFragment.ProfileType.PRIVATE);
                break;
            }
            case ProfileSelectFragment.ProfileType.PERSONAL: {
                // Use the parent user of the current user if the current user is profile.
                final UserHandle currentUser = UserHandle.of(currentUserId);
                final UserHandle userProfileParent = userManager.getProfileParent(currentUser);
                if (userProfileParent != null) {
                    newUserId = userProfileParent.getIdentifier();
                } else {
                    newUserId = currentUserId;
                }
                break;
            }
            default:
                newUserId = currentUserId;
        }

        mUserId = newUserId;
        mIm = mContext.getSystemService(InputManager.class);
        mImm = mContext.getSystemService(InputMethodManager.class);
        mInputDeviceId = -1;

        Activity activity = Preconditions.checkNotNull(getActivity());
        InputDevice inputDeviceFromIntent =
                activity.getIntent().getParcelableExtra(
                        InputPeripheralsSettingsUtils.EXTRA_INPUT_DEVICE,
                        InputDevice.class);

        if (inputDeviceFromIntent != null) {
            launchLayoutPickerWithIdentifier(inputDeviceFromIntent.getIdentifier());
        }
    }

    @Override
    public void onActivityCreated(final Bundle icicle) {
        super.onActivityCreated(icicle);
        Bundle arguments = getArguments();
        if (arguments == null) {
            Log.e(TAG, "Arguments should not be null");
            return;
        }
        mInputDeviceIdentifier =
                arguments.getParcelable(
                        InputPeripheralsSettingsUtils.EXTRA_INPUT_DEVICE_IDENTIFIER,
                        InputDeviceIdentifier.class);
        if (mInputDeviceIdentifier == null) {
            Log.e(TAG, "The inputDeviceIdentifier should not be null");
            return;
        }
        InputDevice inputDevice =
                InputPeripheralsSettingsUtils.getInputDevice(mIm, mInputDeviceIdentifier);
        if (inputDevice == null) {
            Log.e(TAG, "inputDevice is null");
            return;
        }
        final String title = inputDevice.getName();
        getActivity().setTitle(title);
    }

    @Override
    public void onStart() {
        super.onStart();
        mIm.registerInputDeviceListener(this, null);
        InputDevice inputDevice =
                InputPeripheralsSettingsUtils.getInputDevice(mIm, mInputDeviceIdentifier);
        if (inputDevice == null) {
            Log.e(TAG, "Unable to start: input device is null");
            getActivity().finish();
            return;
        }
        mInputDeviceId = inputDevice.getId();
    }

    @Override
    public void onResume() {
        super.onResume();
        updateCheckedState();
    }

    @Override
    public void onStop() {
        super.onStop();
        mIm.unregisterInputDeviceListener(this);
        mInputDeviceId = -1;
    }

    private void launchLayoutPickerWithIdentifier(
            InputDeviceIdentifier inputDeviceIdentifier) {
        if (InputPeripheralsSettingsUtils.getInputDevice(mIm, inputDeviceIdentifier) == null) {
            return;
        }
        InputMethodInfo info = mImm.getCurrentInputMethodInfoAsUser(UserHandle.of(mUserId));
        InputMethodSubtype subtype = mImm.getCurrentInputMethodSubtype();
        CharSequence subtypeLabel = getSubtypeLabel(mContext, info, subtype);

        showKeyboardLayoutPicker(
                subtypeLabel,
                inputDeviceIdentifier,
                mUserId,
                info,
                subtype);
    }

    private void updateCheckedState() {
        if (InputPeripheralsSettingsUtils.getInputDevice(mIm, mInputDeviceIdentifier) == null) {
            return;
        }

        PreferenceScreen preferenceScreen = getPreferenceScreen();
        preferenceScreen.removeAll();
        List<InputMethodInfo> infoList =
                mImm.getEnabledInputMethodListAsUser(UserHandle.of(mUserId));

        // Remove IMEs with no suitable ime subtypes
        infoList.removeIf(imeInfo -> {
            List<InputMethodSubtype> subtypes =
                    mImm.getEnabledInputMethodSubtypeListAsUser(imeInfo.getId(), true,
                            UserHandle.of(mUserId));
            for (InputMethodSubtype subtype : subtypes) {
                if (subtype.isSuitableForPhysicalKeyboardLayoutMapping()) {
                    return false;
                }
            }
            return true;
        });
        Collections.sort(infoList, new Comparator<InputMethodInfo>() {
            public int compare(InputMethodInfo o1, InputMethodInfo o2) {
                String s1 = o1.loadLabel(mContext.getPackageManager()).toString();
                String s2 = o2.loadLabel(mContext.getPackageManager()).toString();
                return s1.compareTo(s2);
            }
        });

        for (InputMethodInfo info : infoList) {
            mKeyboardInfoList.clear();
            List<InputMethodSubtype> subtypes =
                    mImm.getEnabledInputMethodSubtypeListAsUser(info.getId(), true,
                            UserHandle.of(mUserId));
            for (InputMethodSubtype subtype : subtypes) {
                if (subtype.isSuitableForPhysicalKeyboardLayoutMapping()) {
                    mapLanguageWithLayout(info, subtype);
                }
            }
            updatePreferenceLayout(preferenceScreen, info, infoList.size() > 1);
        }
    }

    private void mapLanguageWithLayout(InputMethodInfo info, InputMethodSubtype subtype) {
        CharSequence subtypeLabel = getSubtypeLabel(mContext, info, subtype);
        KeyboardLayout[] keyboardLayouts =
                InputPeripheralsSettingsUtils.getKeyboardLayouts(
                        mIm, mUserId, mInputDeviceIdentifier, info, subtype);
        KeyboardLayoutSelectionResult result = InputPeripheralsSettingsUtils.getKeyboardLayout(
                mIm, mUserId, mInputDeviceIdentifier, info, subtype);
        if (result.getLayoutDescriptor() != null) {
            for (int i = 0; i < keyboardLayouts.length; i++) {
                if (keyboardLayouts[i].getDescriptor().equals(result.getLayoutDescriptor())) {
                    KeyboardInfo keyboardInfo = new KeyboardInfo(
                            subtypeLabel,
                            keyboardLayouts[i].getLabel(),
                            result.getSelectionCriteria(),
                            info,
                            subtype);
                    mKeyboardInfoList.add(keyboardInfo);
                    break;
                }
            }
        } else {
            // if there is no auto-selected layout, we should show "Default"
            KeyboardInfo keyboardInfo = new KeyboardInfo(
                    subtypeLabel,
                    mContext.getString(R.string.keyboard_default_layout),
                    KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_UNSPECIFIED,
                    info,
                    subtype);
            mKeyboardInfoList.add(keyboardInfo);
        }
    }

    private void updatePreferenceLayout(PreferenceScreen preferenceScreen, InputMethodInfo info,
            boolean hasMultipleImes) {
        if (mKeyboardInfoList.isEmpty()) {
            return;
        }
        PreferenceCategory preferenceCategory = new PreferenceCategory(mContext);
        preferenceCategory.setTitle(hasMultipleImes ? mContext.getString(R.string.ime_label_title,
                info.loadLabel(mContext.getPackageManager()))
                : mContext.getString(R.string.enabled_locales_keyboard_layout));
        preferenceCategory.setKey(info.getPackageName());
        preferenceScreen.addPreference(preferenceCategory);
        Collections.sort(mKeyboardInfoList, new Comparator<KeyboardInfo>() {
            public int compare(KeyboardInfo o1, KeyboardInfo o2) {
                String s1 = o1.getSubtypeLabel().toString();
                String s2 = o2.getSubtypeLabel().toString();
                return s1.compareTo(s2);
            }
        });

        for (KeyboardInfo keyboardInfo : mKeyboardInfoList) {
            final Preference pref = new Preference(mContext);
            pref.setKey(keyboardInfo.getPrefId());
            pref.setTitle(keyboardInfo.getSubtypeLabel());
            pref.setSummary(keyboardInfo.getLayoutSummaryText(mContext));
            pref.setOnPreferenceClickListener(
                    preference -> {
                        showKeyboardLayoutPicker(
                                keyboardInfo.getSubtypeLabel(),
                                mInputDeviceIdentifier,
                                mUserId,
                                keyboardInfo.getInputMethodInfo(),
                                keyboardInfo.getInputMethodSubtype());
                        return true;
                    });
            preferenceCategory.addPreference(pref);
        }
    }

    @Override
    public void onInputDeviceAdded(int deviceId) {
        // Do nothing.
    }

    @Override
    public void onInputDeviceRemoved(int deviceId) {
        if (mInputDeviceId >= 0 && deviceId == mInputDeviceId) {
            getActivity().finish();
        }
    }

    @Override
    public void onInputDeviceChanged(int deviceId) {
        if (mInputDeviceId >= 0 && deviceId == mInputDeviceId) {
            updateCheckedState();
        }
    }

    @Override
    protected String getLogTag() {
        return TAG;
    }

    @Override
    public int getMetricsCategory() {
        return SettingsEnums.SETTINGS_KEYBOARDS_ENABLED_LOCALES;
    }

    @Override
    protected int getPreferenceScreenResId() {
        return R.xml.keyboard_settings_enabled_locales_list;
    }

    private void showKeyboardLayoutPicker(
            CharSequence subtypeLabel,
            InputDeviceIdentifier inputDeviceIdentifier,
            int userId,
            InputMethodInfo inputMethodInfo,
            InputMethodSubtype inputMethodSubtype) {
        Bundle arguments = new Bundle();
        arguments.putParcelable(
                InputPeripheralsSettingsUtils.EXTRA_INPUT_DEVICE_IDENTIFIER,
                inputDeviceIdentifier);
        arguments.putParcelable(
                InputPeripheralsSettingsUtils.EXTRA_INPUT_METHOD_INFO, inputMethodInfo);
        arguments.putParcelable(
                InputPeripheralsSettingsUtils.EXTRA_INPUT_METHOD_SUBTYPE, inputMethodSubtype);
        arguments.putInt(InputPeripheralsSettingsUtils.EXTRA_USER_ID, userId);
        arguments.putCharSequence(InputPeripheralsSettingsUtils.EXTRA_TITLE, subtypeLabel);
        new SubSettingLauncher(mContext)
                .setSourceMetricsCategory(getMetricsCategory())
                .setDestination(NewKeyboardLayoutPickerFragment.class.getName())
                .setArguments(arguments)
                .launch();
    }

    private CharSequence getSubtypeLabel(
            Context context, InputMethodInfo info, InputMethodSubtype subtype) {
        return subtype.getDisplayName(
                context, info.getPackageName(), info.getServiceInfo().applicationInfo);
    }
}
