/*
 * Copyright (C) 2007 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.stk;

import android.app.AlarmManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.SystemClock;
import android.telephony.CarrierConfigManager;
import android.text.Editable;
import android.text.InputFilter;
import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.method.PasswordTransformationMethod;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.TextView.BufferType;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import com.android.internal.telephony.cat.CatLog;
import com.android.internal.telephony.cat.Input;

import com.google.android.material.textfield.TextInputLayout;

/**
 * Display a request for a text input a long with a text edit form.
 */
public class StkInputActivity extends AppCompatActivity implements View.OnClickListener,
        TextWatcher {

    // Members
    private int mState;
    private EditText mTextIn = null;
    private TextView mPromptView = null;
    private View mMoreOptions = null;
    private PopupMenu mPopupMenu = null;
    private View mYesNoLayout = null;
    private View mNormalLayout = null;

    // Constants
    private static final String LOG_TAG = StkInputActivity.class.getSimpleName();

    private Input mStkInput = null;
    // Constants
    private static final int STATE_TEXT = 1;
    private static final int STATE_YES_NO = 2;

    static final String YES_STR_RESPONSE = "YES";
    static final String NO_STR_RESPONSE = "NO";

    // Font size factor values.
    static final float NORMAL_FONT_FACTOR = 1;
    static final float LARGE_FONT_FACTOR = 2;
    static final float SMALL_FONT_FACTOR = (1 / 2);

    // Keys for saving the state of the activity in the bundle
    private static final String RESPONSE_SENT_KEY = "response_sent";
    private static final String INPUT_STRING_KEY = "input_string";
    private static final String ALARM_TIME_KEY = "alarm_time";

    private static final String INPUT_ALARM_TAG = LOG_TAG;
    private static final long NO_INPUT_ALARM = -1;
    private long mAlarmTime = NO_INPUT_ALARM;

    private StkAppService appService = StkAppService.getInstance();

    private boolean mIsResponseSent = false;
    private int mSlotId = -1;

    // Click listener to handle buttons press..
    public void onClick(View v) {
        String input = null;
        if (mIsResponseSent) {
            CatLog.d(LOG_TAG, "Already responded");
            return;
        }

        switch (v.getId()) {
        case R.id.button_ok:
            input = mTextIn.getText().toString();
            break;
        case R.id.button_cancel:
            sendResponse(StkAppService.RES_ID_END_SESSION);
            finish();
            return;
        // Yes/No layout buttons.
        case R.id.button_yes:
            input = YES_STR_RESPONSE;
            break;
        case R.id.button_no:
            input = NO_STR_RESPONSE;
            break;
        case R.id.more:
            if (mPopupMenu == null) {
                mPopupMenu = new PopupMenu(this, v);
                Menu menu = mPopupMenu.getMenu();
                createOptionsMenuInternal(menu);
                prepareOptionsMenuInternal(menu);
                mPopupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
                    public boolean onMenuItemClick(MenuItem item) {
                        optionsItemSelectedInternal(item);
                        return true;
                    }
                });
                mPopupMenu.setOnDismissListener(new PopupMenu.OnDismissListener() {
                    public void onDismiss(PopupMenu menu) {
                        mPopupMenu = null;
                    }
                });
                mPopupMenu.show();
            }
            return;
        default:
            break;
        }
        CatLog.d(LOG_TAG, "handleClick, ready to response");
        sendResponse(StkAppService.RES_ID_INPUT, input, false);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().addSystemFlags(
                WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
        CatLog.d(LOG_TAG, "onCreate - mIsResponseSent[" + mIsResponseSent + "]");

        // appService can be null if this activity is automatically recreated by the system
        // with the saved instance state right after the phone process is killed.
        if (appService == null) {
            CatLog.d(LOG_TAG, "onCreate - appService is null");
            finish();
            return;
        }

        // Set the layout for this activity.
        setContentView(R.layout.stk_input);
        setSupportActionBar((Toolbar) findViewById(R.id.toolbar));

        if (getResources().getBoolean(R.bool.show_menu_title_only_on_menu)) {
            getSupportActionBar().hide();

            mMoreOptions = findViewById(R.id.more);
            mMoreOptions.setVisibility(View.VISIBLE);
            mMoreOptions.setOnClickListener(this);
        }

        // Initialize members
        mTextIn = (EditText) this.findViewById(R.id.in_text);
        mPromptView = (TextView) this.findViewById(R.id.prompt);
        // Set buttons listeners.
        Button okButton = (Button) findViewById(R.id.button_ok);
        Button cancelButton = (Button) findViewById(R.id.button_cancel);
        Button yesButton = (Button) findViewById(R.id.button_yes);
        Button noButton = (Button) findViewById(R.id.button_no);

        okButton.setOnClickListener(this);
        cancelButton.setOnClickListener(this);
        yesButton.setOnClickListener(this);
        noButton.setOnClickListener(this);

        mYesNoLayout = findViewById(R.id.yes_no_layout);
        mNormalLayout = findViewById(R.id.normal_layout);
        initFromIntent(getIntent());
        appService.getStkContext(mSlotId).setPendingActivityInstance(this);
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);

        mTextIn.addTextChangedListener(this);
    }

    @Override
    public void onResume() {
        super.onResume();
        CatLog.d(LOG_TAG, "onResume - mIsResponseSent[" + mIsResponseSent +
                "], slot id: " + mSlotId);
        if (mAlarmTime == NO_INPUT_ALARM) {
            startTimeOut();
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        CatLog.d(LOG_TAG, "onPause - mIsResponseSent[" + mIsResponseSent + "]");
        if (mPopupMenu != null) {
            mPopupMenu.dismiss();
        }
        if (mTextIn != null) {
            InputMethodManager imm = getSystemService(InputMethodManager.class);
            imm.hideSoftInputFromWindow(mTextIn.getWindowToken(), 0);
        }
    }

    @Override
    public void onStop() {
        super.onStop();
        CatLog.d(LOG_TAG, "onStop - mIsResponseSent[" + mIsResponseSent + "]");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        CatLog.d(LOG_TAG, "onDestroy - before Send End Session mIsResponseSent[" +
                mIsResponseSent + " , " + mSlotId + "]");
        if (appService == null) {
            return;
        }
        // Avoid sending the terminal response while the activty is being restarted
        // due to some kind of configuration change.
        if (!isChangingConfigurations()) {
            // If the input activity is finished by stkappservice
            // when receiving OP_LAUNCH_APP from the other SIM, we can not send TR here,
            // since the input cmd is waiting user to process.
            if (!mIsResponseSent && !appService.isInputPending(mSlotId)) {
                CatLog.d(LOG_TAG, "handleDestroy - Send End Session");
                sendResponse(StkAppService.RES_ID_END_SESSION);
            }
        }
        cancelTimeOut();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if (mPopupMenu != null) {
            mPopupMenu.dismiss();
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (mIsResponseSent) {
            CatLog.d(LOG_TAG, "Already responded");
            return true;
        }

        switch (keyCode) {
        case KeyEvent.KEYCODE_BACK:
            CatLog.d(LOG_TAG, "onKeyDown - KEYCODE_BACK");
            sendResponse(StkAppService.RES_ID_BACKWARD, null, false);
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }

    void sendResponse(int resId) {
        sendResponse(resId, null, false);
    }

    void sendResponse(int resId, String input, boolean help) {
        cancelTimeOut();

        if (mSlotId == -1) {
            CatLog.d(LOG_TAG, "slot id is invalid");
            return;
        }

        if (StkAppService.getInstance() == null) {
            CatLog.d(LOG_TAG, "StkAppService is null, Ignore response: id is " + resId);
            return;
        }

        if (mMoreOptions != null) {
            mMoreOptions.setVisibility(View.INVISIBLE);
        }

        CatLog.d(LOG_TAG, "sendResponse resID[" + resId + "] input[*****] help[" 
                + help + "]");
        mIsResponseSent = true;
        Bundle args = new Bundle();
        args.putInt(StkAppService.RES_ID, resId);
        if (input != null) {
            args.putString(StkAppService.INPUT, input);
        }
        args.putBoolean(StkAppService.HELP, help);
        appService.sendResponse(args, mSlotId);
    }

    @Override
    public boolean onCreateOptionsMenu(android.view.Menu menu) {
        super.onCreateOptionsMenu(menu);
        createOptionsMenuInternal(menu);
        return true;
    }

    private void createOptionsMenuInternal(Menu menu) {
        menu.add(Menu.NONE, StkApp.MENU_ID_END_SESSION, 1, R.string.menu_end_session);
        menu.add(0, StkApp.MENU_ID_HELP, 2, R.string.help);
    }

    @Override
    public boolean onPrepareOptionsMenu(android.view.Menu menu) {
        super.onPrepareOptionsMenu(menu);
        prepareOptionsMenuInternal(menu);
        return true;
    }

    private void prepareOptionsMenuInternal(Menu menu) {
        menu.findItem(StkApp.MENU_ID_END_SESSION).setVisible(true);
        menu.findItem(StkApp.MENU_ID_HELP).setVisible(mStkInput.helpAvailable);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (optionsItemSelectedInternal(item)) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    private boolean optionsItemSelectedInternal(MenuItem item) {
        if (mIsResponseSent) {
            CatLog.d(LOG_TAG, "Already responded");
            return true;
        }
        switch (item.getItemId()) {
        case StkApp.MENU_ID_END_SESSION:
            sendResponse(StkAppService.RES_ID_END_SESSION);
            finish();
            return true;
        case StkApp.MENU_ID_HELP:
            sendResponse(StkAppService.RES_ID_INPUT, "", true);
            return true;
        }
        return false;
    }

    @SuppressWarnings("MissingSuperCall") // TODO: Fix me
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        CatLog.d(LOG_TAG, "onSaveInstanceState: " + mSlotId);
        outState.putBoolean(RESPONSE_SENT_KEY, mIsResponseSent);
        outState.putString(INPUT_STRING_KEY, mTextIn.getText().toString());
        outState.putLong(ALARM_TIME_KEY, mAlarmTime);
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        CatLog.d(LOG_TAG, "onRestoreInstanceState: " + mSlotId);

        mIsResponseSent = savedInstanceState.getBoolean(RESPONSE_SENT_KEY);
        if (mIsResponseSent && (mMoreOptions != null)) {
            mMoreOptions.setVisibility(View.INVISIBLE);
        }

        String savedString = savedInstanceState.getString(INPUT_STRING_KEY);
        mTextIn.setText(savedString);
        updateButton();

        mAlarmTime = savedInstanceState.getLong(ALARM_TIME_KEY, NO_INPUT_ALARM);
        if (mAlarmTime != NO_INPUT_ALARM) {
            startTimeOut();
        }
    }

    public void beforeTextChanged(CharSequence s, int start, int count,
            int after) {
    }

    public void onTextChanged(CharSequence s, int start, int before, int count) {
        // Reset timeout.
        cancelTimeOut();
        startTimeOut();
        updateButton();
    }

    public void afterTextChanged(Editable s) {
    }

    private void updateButton() {
        // Disable the button if the length of the input text does not meet the expectation.
        Button okButton = (Button) findViewById(R.id.button_ok);
        okButton.setEnabled((mTextIn.getText().length() < mStkInput.minLen) ? false : true);
    }

    private void cancelTimeOut() {
        if (mAlarmTime != NO_INPUT_ALARM) {
            CatLog.d(LOG_TAG, "cancelTimeOut - slot id: " + mSlotId);
            AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
            am.cancel(mAlarmListener);
            mAlarmTime = NO_INPUT_ALARM;
        }
    }

    private void startTimeOut() {
        // No need to set alarm if device sent TERMINAL RESPONSE already.
        if (mIsResponseSent) {
            return;
        }

        if (mAlarmTime == NO_INPUT_ALARM) {
            int duration = StkApp.calculateDurationInMilis(mStkInput.duration);
            if (duration <= 0) {
                duration = StkApp.UI_TIMEOUT;
            }
            mAlarmTime = SystemClock.elapsedRealtime() + duration;
        }

        CatLog.d(LOG_TAG, "startTimeOut: " + mAlarmTime + "ms, slot id: " + mSlotId);
        AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        am.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, mAlarmTime, INPUT_ALARM_TAG,
                mAlarmListener, null);
    }

    private void configInputDisplay() {
        TextInputLayout textInput = (TextInputLayout) findViewById(R.id.text_input_layout);

        int inTypeId = R.string.alphabet;

        // set the prompt.
        if ((mStkInput.icon == null || !mStkInput.iconSelfExplanatory)
                && !TextUtils.isEmpty(mStkInput.text)) {
            mPromptView.setText(mStkInput.text);
            mPromptView.setVisibility(View.VISIBLE);
        }

        // Set input type (alphabet/digit) info close to the InText form.
        boolean hideHelper = false;
        if (mStkInput.digitOnly) {
            mTextIn.setKeyListener(StkDigitsKeyListener.getInstance());
            mTextIn.setInputType(InputType.TYPE_CLASS_PHONE);
            inTypeId = R.string.digits;
            hideHelper = StkAppService.getBooleanCarrierConfig(this,
                    CarrierConfigManager.KEY_HIDE_DIGITS_HELPER_TEXT_ON_STK_INPUT_SCREEN_BOOL,
                    mSlotId);
        }
        textInput.setHelperText(getResources().getString(inTypeId));
        textInput.setHelperTextEnabled(!hideHelper);
        CatLog.d(LOG_TAG,
                String.format("configInputDisplay: digitOnly=%s, hideHelper=%s",
                        mStkInput.digitOnly, hideHelper));
        setTitle(R.string.app_name);

        if (mStkInput.icon != null) {
            ImageView imageView = (ImageView) findViewById(R.id.icon);
            imageView.setImageBitmap(mStkInput.icon);
            imageView.setVisibility(View.VISIBLE);
        }

        // Handle specific global and text attributes.
        switch (mState) {
        case STATE_TEXT:
            mTextIn.setFilters(new InputFilter[] {new InputFilter.LengthFilter(mStkInput.maxLen)});

            textInput.setCounterMaxLength(mStkInput.maxLen);
            //do not show the length helper for the text input
            textInput.setCounterEnabled(false);

            if (!mStkInput.echo) {
                mTextIn.setTransformationMethod(PasswordTransformationMethod
                        .getInstance());
            }
            mTextIn.setImeOptions(EditorInfo.IME_FLAG_NO_FULLSCREEN);
            // Request the initial focus on the edit box and show the software keyboard.
            mTextIn.requestFocus();
            getWindow().setSoftInputMode(
                    WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
            // Set default text if present.
            if (mStkInput.defaultText != null) {
                mTextIn.setText(mStkInput.defaultText);
            } else {
                // make sure the text is cleared
                mTextIn.setText("", BufferType.EDITABLE);
            }
            updateButton();

            break;
        case STATE_YES_NO:
            // Set display mode - normal / yes-no layout
            mYesNoLayout.setVisibility(View.VISIBLE);
            mNormalLayout.setVisibility(View.GONE);
            break;
        }
    }

    private void initFromIntent(Intent intent) {
        // Get the calling intent type: text/key, and setup the
        // display parameters.
        CatLog.d(LOG_TAG, "initFromIntent - slot id: " + mSlotId);
        if (intent != null) {
            mStkInput = intent.getParcelableExtra("INPUT");
            mSlotId = intent.getIntExtra(StkAppService.SLOT_ID, -1);
            CatLog.d(LOG_TAG, "onCreate - slot id: " + mSlotId);
            if (mStkInput == null) {
                finish();
            } else {
                mState = mStkInput.yesNo ? STATE_YES_NO :
                        STATE_TEXT;
                configInputDisplay();
            }
        } else {
            finish();
        }
    }

    private final AlarmManager.OnAlarmListener mAlarmListener =
            new AlarmManager.OnAlarmListener() {
                @Override
                public void onAlarm() {
                    CatLog.d(LOG_TAG, "The alarm time is reached");
                    mAlarmTime = NO_INPUT_ALARM;
                    sendResponse(StkAppService.RES_ID_TIMEOUT);
                }
            };
}
