/*
* Copyright 2014 Qianqian Zhu <[email protected]> All rights reserved.
*
* 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.z299studio.pb;

import android.Manifest;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.annotation.TargetApi;
import android.content.pm.PackageManager;
import android.content.res.TypedArray;
import android.graphics.PorterDuff;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import androidx.appcompat.app.AppCompatActivity;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import android.widget.Toast;

import java.lang.ref.WeakReference;

public class HomeActivity extends AppCompatActivity implements
AnimatorListener, SyncService.SyncListener, BiometricAuthHelper.BiometricListener,
        DecryptTask.OnTaskFinishListener{
    protected Application mApp;
    protected EditText mPwdEdit;
    protected TextView mSyncText;
    protected View mButtonContainer;
    private ProgressBar mProgress;
    private static int mStage;
    private static final int SELECT_SYNC = 0;
    private static final int LOADING = 1;
    private static final int SET_PWD = 2;
    private static final int AUTH = 3;
    private FingerprintManager mFingerprintManager;
    private static final int PERMISSION_REQ_CODE_FP = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        mApp = Application.getInstance(this);
        this.setTheme(C.THEMES[Application.Options.mTheme]);
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_home);
        int[] primaryColors = {R.attr.colorPrimary, R.attr.colorPrimaryDark,
                R.attr.colorAccent, R.attr.textColorNormal, R.attr.iconColorNormal};
        TypedArray ta = obtainStyledAttributes(primaryColors);
        for(int i = 0; i < C.ThemedColors.length; ++i) {
            C.ThemedColors[i] = ta.getColor(i, 0);
        }
        ta.recycle();
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            View v = findViewById(R.id.activity_root);
            v.setBackgroundColor(C.ThemedColors[C.colorPrimary]);
        }
        if(savedInstanceState==null) {
            if(!mApp.hasDataFile(this)) {
                mStage = SELECT_SYNC;
            }
            else {
                mStage = AUTH;
            }
            getSupportFragmentManager().beginTransaction().add(R.id.container,
                HomeFragment.create()).commitAllowingStateLoss();
        }
        else {
            mStage = savedInstanceState.getInt("home_stage");
        }
        if(!Application.Options.mTour){
            Intent intent = new Intent(this, TourActivity.class);
            intent.putExtra(C.ACTIVITY, C.Activity.HOME);
            this.startActivity(intent);
            this.finish();
            overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
        }
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ){
            mFingerprintManager = (FingerprintManager)getSystemService(FINGERPRINT_SERVICE);
            if(mStage == AUTH && mApp.queryFpStatus() == C.Fingerprint.ENABLED) {
                tryUseFingerprint();
            }
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        if(mStage == LOADING) {
            startSync(false);
        }
        else {
            mPwdEdit = findViewById(R.id.password);
        }
    }

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

    @Override
    protected void onResume() {
        super.onResume();
        popInput();
    }

    @Override
    protected void onPause() {
        if(mPwdEdit!=null) {
            mPwdEdit.clearFocus();
            InputMethodManager imm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
            assert imm != null;
            imm.hideSoftInputFromWindow(mPwdEdit.getWindowToken(), 0);
        }
        super.onPause();
    }

    public void onConfirm(View view) {
        String password = mPwdEdit.getText().toString();
        if(password.length() < 1) {
            Application.showToast(this, R.string.pwd_wrong, Toast.LENGTH_SHORT);
            return;
        }
        if(mStage == SET_PWD) {
            EditText et_confirm = findViewById(R.id.confirm);
            if(password.equals(et_confirm.getText().toString())) {
                et_confirm.clearFocus();
                InputMethodManager imm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
                if (imm != null) {
                        imm.hideSoftInputFromWindow(et_confirm.getWindowToken(), 0);
                }
                mApp.setPassword(password, true);
                new InitTask(this).execute();
            }
            else {
                et_confirm.setText("");
                mPwdEdit.setText("");
                Application.showToast(this, R.string.pwd_unmatch, Toast.LENGTH_SHORT);
            }
        }
        else {
            mApp.setPassword(password, false);
            new DecryptTask(mApp.getData(this), mApp.getAppHeaderData(this), this).execute(password);
        }
    }

    @Override @TargetApi(Build.VERSION_CODES.M)
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResult){
        if(requestCode == PERMISSION_REQ_CODE_FP &&
                grantResult[0] == PackageManager.PERMISSION_GRANTED){
            BiometricAuthHelper authHelper = new BiometricAuthHelper(Application.Options.mFpStatus == C.Fingerprint.UNKNOWN, this, this);
            authHelper.authenticate();
        }
    }

    @TargetApi(Build.VERSION_CODES.M)
    private boolean tryUseFingerprint() {
        boolean isHandled = false;
        if(Application.Options.mFpStatus != C.Fingerprint.DISABLED){
            if(checkSelfPermission(Manifest.permission.USE_FINGERPRINT)
                    != PackageManager.PERMISSION_GRANTED) {
                requestPermissions(new String[] {Manifest.permission.USE_FINGERPRINT},
                        PERMISSION_REQ_CODE_FP);
                isHandled = true;
            }
            else if(mFingerprintManager.isHardwareDetected() &&
                    mFingerprintManager.hasEnrolledFingerprints()) {
                BiometricAuthHelper authHelper = new BiometricAuthHelper(
                        Application.Options.mFpStatus == C.Fingerprint.UNKNOWN, this, this);
                if(mPwdEdit!=null) {
                    InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
                    if (imm != null) {
                        imm.hideSoftInputFromWindow(mPwdEdit.getWindowToken(), 0);
                    }
                }
                authHelper.authenticate();
                isHandled = true;
            }
        }
        return isHandled;
    }

    public void onSyncSelected(View view) {
        int id = view.getId();
        switch(id) {
        case R.id.btn_gdrive:
        case R.id.btn_gpg:
            mStage = LOADING;
            Application.Options.mSync = id == R.id.btn_gdrive ? C.Sync.GDRIVE : C.Sync.GPGS;
            startSync(true);
            break;
        case R.id.btn_local:
            Application.Options.mSync = C.Sync.NONE;
            mApp.mSP.edit().putInt(C.Sync.SERVER, Application.Options.mSync).apply();
            mStage = SET_PWD;
            startHome();
            break;
        }
    }

    private void startSync(boolean animationOn) {
        Resources r = getResources();
        int[] strRes = {R.string.sync_none, R.string.sync_gpg, R.string.sync_gdrive};
        mButtonContainer = findViewById(R.id.btn_container);
        if(animationOn) {
            mButtonContainer.animate().scaleY(0.0f).setListener(this);
        }
        else {
            mButtonContainer.setVisibility(View.INVISIBLE);
        }
        mSyncText = findViewById(R.id.sync_hint);
        mSyncText.setText(r.getString(R.string.contacting,
                r.getString(strRes[Application.Options.mSync])));
        ProgressBar pb = findViewById(R.id.pb);
        if(animationOn) {
            pb.animate().alpha(1.0f).setStartDelay(300);
            mSyncText.animate().alpha(1.0f).setStartDelay(300);
        }
        else {
            pb.setAlpha(1.0f);
            mSyncText.setAlpha(1.0f);
        }
        SyncService ss = SyncService.getInstance(Application.Options.mSync);
        ss.initialize(this).setListener(this).connect(this, 0);
    }

    private void startHome() {
        getSupportFragmentManager().beginTransaction()
            .setCustomAnimations(R.anim.expand_from_right, R.anim.collapse_to_left)
            .replace(R.id.container, HomeFragment.create(), null)
            .commitAllowingStateLoss();
        Handler handler = new Handler();
        handler.postDelayed(() -> {
            mPwdEdit = findViewById(R.id.password);
            popInput();
        },300);
    }

    public void startMain() {
        Application.reset();
        Intent intent = new Intent(HomeActivity.this, MainActivity.class);
        mApp.ignoreNextPause();
        mApp.getAccountManager().setDefaultCategory(-1, getString(R.string.def_category));
        startActivity(intent);
        this.finish();
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        SyncService.getInstance().onActivityResult(this, requestCode, resultCode, data);
    }

    private void popInput() {
        if(mPwdEdit!=null) {
            mPwdEdit.postDelayed(() -> {
                mPwdEdit.requestFocus();
                InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                assert imm != null;
                imm.showSoftInput(mPwdEdit, InputMethodManager.SHOW_FORCED);
            }, 100);

            OnEditorActionListener eal = (edit, id, event) -> {
                if(id == EditorInfo.IME_ACTION_DONE) {
                    onConfirm(null);
                    return true;
                }
                return false;
            };
            EditText et_confirm = findViewById(R.id.confirm);
            if(et_confirm.getVisibility() == View.VISIBLE) {
                mPwdEdit.setImeOptions(EditorInfo.IME_ACTION_NEXT);
                et_confirm.setOnEditorActionListener(eal);
            }
            else {
                mPwdEdit.setOnEditorActionListener(eal);
            }
        }
    }

    @Override
    public void preExecute() {
        Button okButton = HomeActivity.this.findViewById(R.id.unlock);
        mProgress = HomeActivity.this.findViewById(R.id.pb);
        okButton.setEnabled(false);
        mProgress.setVisibility(View.VISIBLE);
        mApp.onStart();
    }

    @Override
    public void onFinished(boolean isSuccessful, AccountManager manager, String password,
                           byte[] data, Application.FileHeader header, Crypto crypto) {
        if(isSuccessful) {
            mApp.setAccountManager(manager, -1, getString(R.string.def_category));
            mApp.setCrypto(crypto);
        }
        if(!isSuccessful) {
            mProgress.setVisibility(View.INVISIBLE);
            mPwdEdit.setText("");
            Application.showToast(HomeActivity.this, R.string.pwd_wrong, Toast.LENGTH_SHORT);
        }
        else if(Build.VERSION.SDK_INT < Build.VERSION_CODES.M
                || Application.Options.mFpStatus == C.Fingerprint.ENABLED
                || !tryUseFingerprint())  {
            startMain();
        }
    }

    public static class HomeFragment extends Fragment {

        public static HomeFragment create() {
            return new HomeFragment();
        }

        public HomeFragment() {}
        @Override
        public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            int layout;
            if(mStage == LOADING || mStage == SELECT_SYNC) {
                layout = R.layout.fragment_startup;
            }
            else {
                layout = R.layout.fragment_home;
            }

            View rootView = inflater.inflate(layout, container, false);

            if(layout == R.layout.fragment_home) {
                final Button unlock = rootView.findViewById(R.id.unlock);
                if(Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                    unlock.getBackground().setColorFilter(C.ThemedColors[C.colorAccent],
                            PorterDuff.Mode.SRC_ATOP);
                }
                TextWatcher tw = new TextWatcher() {
                    @Override
                    public void afterTextChanged(Editable s) {
                        if(s.toString().length() < 1) {
                            unlock.setEnabled(false);
                        }
                        else {
                            unlock.setEnabled(true);
                        }
                    }
                    @Override
                    public void beforeTextChanged(CharSequence s, int start,
                            int count, int after) {    }

                    @Override
                    public void onTextChanged(CharSequence s, int start,
                            int before, int count) { }
                };
                EditText password = rootView.findViewById(R.id.password);
                password.addTextChangedListener(tw);
                if(mStage == AUTH) {
                    EditText confirm = rootView.findViewById(R.id.confirm);
                    confirm.setVisibility(View.GONE);
                }
                else if(mStage == SET_PWD) {
                    TextView tv = rootView.findViewById(R.id.title);
                    tv.setText(R.string.set_pwd);
                    unlock.setText(R.string.get_started);
                }
            }
            else if(Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP){
                int[] ids = {R.id.btn_local, R.id.btn_gdrive, R.id.btn_gpg};
                for(int id : ids) {
                    View button = rootView.findViewById(id);
                    button.getBackground().setColorFilter(C.ThemedColors[C.colorAccent],
                            PorterDuff.Mode.SRC_ATOP);
                }
            }
            return rootView;
        }
    }
    @Override
    public void onAnimationStart(Animator animation) {}

    @Override
    public void onAnimationEnd(Animator animation) {
        mButtonContainer.setVisibility(View.INVISIBLE);
    }

    @Override
    public void onAnimationCancel(Animator animation) {}
    @Override
    public void onAnimationRepeat(Animator animation) {}

    @Override
    public void onSyncFailed(int errorCode) {
        mStage = SET_PWD;
        if(errorCode == SyncService.CA.AUTH) {
            Application.Options.mSync = C.Sync.NONE;
            mApp.mSP.edit().putInt(C.Sync.SERVER, C.Sync.NONE).apply();
        }
        startHome();
    }

    @Override
    public void onSyncProgress(int actionCode) {
        if(actionCode == SyncService.CA.AUTH) {
            Resources r = getResources();
            mSyncText.setText(r.getString(R.string.loading,
                 Application.Options.mSync == C.Sync.GDRIVE ?
                     r.getString(R.string.sync_gdrive) : r.getString(R.string.sync_gpg)));
            mApp.mSP.edit().putInt(C.Sync.SERVER, Application.Options.mSync).apply();
        } else if(actionCode == SyncService.CA.DATA_RECEIVED) {
            byte[] data = SyncService.getInstance().requestData();
            Application.getInstance().onSyncSucceed();
            Application.FileHeader fh = Application.FileHeader.parse(data);
            if(fh.valid) {
                mApp.onVersionUpdated(fh.revision);
                mApp.saveData(this, data, fh);
                mStage = AUTH;
            }
            else {
                mStage = SET_PWD;
            }
            startHome();
        }
    }

    @Override
    public void onCanceled(boolean isFirstTime) {
        if(isFirstTime) {
            startMain();
        }
        else {
            popInput();
        }
    }

    @Override
    public void onConfirmed(boolean isFirstTime, byte[] password) {
        if(isFirstTime) {
            startMain();
        }
        else {
            String pwd = new String(password);
            mApp.setPassword(pwd, false);
            new DecryptTask(mApp.getData(this), mApp.getAppHeaderData(this), this).execute(pwd);
        }
    }

    private static class InitTask extends AsyncTask<String, Void, String> {

        private WeakReference<HomeActivity> activityRef;

        InitTask(HomeActivity homeActivity) {
            this.activityRef = new WeakReference<>(homeActivity);
        }

        @Override
        protected void onPreExecute() {
            Button okButton = activityRef.get().findViewById(R.id.unlock);
            ProgressBar pb = activityRef.get().findViewById(R.id.pb);
            okButton.setEnabled(false);
            pb.setVisibility(View.VISIBLE);
        }
        @Override
        protected String doInBackground(String... params) {
            activityRef.get().mApp.onStart();
            Resources r = activityRef.get().getResources();
            String[] defCategories = r.getStringArray(R.array.category_names);
            int i = 0;
            AccountManager am = new AccountManager(null);
            activityRef.get().mApp.setAccountManager(am, -1, activityRef.get().getString(R.string.def_category));
            for(String s : defCategories) {
                am.addCategory(i++, s);
            }
            String[] defAccountNames = r.getStringArray(R.array.def_account_names);
            String[] defNames = r.getStringArray(R.array.def_field_names);
            String[] defValues = r.getStringArray(R.array.def_values);
            int[] defTypes = r.getIntArray(R.array.def_field_types);
            TypedArray defAccountData = r.obtainTypedArray(R.array.def_account_data);
            int[] dataDetails;
            AccountManager.Account a;
            for (i = 0; i < defAccountNames.length; ++i) {
                dataDetails = r.getIntArray(defAccountData.getResourceId(i, 0));
                a = am.newAccount(dataDetails[0]);
                a.mProfile = defAccountNames[i];
                for (int j = 1; j < dataDetails.length; j += 3){
                    try {
                        a.addEntry(defTypes[dataDetails[j + 2]],
                                defNames[dataDetails[j]],
                                defValues[dataDetails[j + 1]]);
                    } catch (ArrayIndexOutOfBoundsException e) {
                        Log.e("Passbook", "This should really not happen.");
                    }
                }
                am.addAccount(dataDetails[0], a);
            }
            defAccountData.recycle();
            return "OK";
        }

        @Override
        protected void onPostExecute(String result) {
            if(Build.VERSION.SDK_INT < Build.VERSION_CODES.M
                    || Application.Options.mFpStatus == C.Fingerprint.ENABLED
                    || !activityRef.get().tryUseFingerprint())  {
                activityRef.get().startMain();
            }
        }
    }
}