/*
 * Copyright (C) 2014 The CyanogenMod 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.cyanogenmod;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.UserHandle;
import android.preference.PreferenceManager;
import android.text.InputFilter;
import android.text.LoginFilter;
import android.util.AttributeSet;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Toast;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.applications.LockPatternActivity;

import java.io.IOException;

/**
 * When the user forgets their password a bunch of times, we fall back on their
 * account's login/password to unlock protected apps (and reset their lock pattern).
 */
public class ProtectedAccountView extends LinearLayout implements View.OnClickListener {

    public static interface OnNotifyAccountReset {
        void onNotifyAccountReset();
    }

    private EditText mLogin;
    private EditText mPassword;
    private Button mOk;
    private Context mContext;
    private LockPatternUtils mLockPatternUtils;
    private OnNotifyAccountReset mNotifyAccountResetCb;

    /**
     * Shown while making asynchronous check of password.
     */
    private ProgressDialog mCheckingDialog;

    public ProtectedAccountView(Context context) {
        this(context, null);
    }

    public ProtectedAccountView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ProtectedAccountView(Context context, AttributeSet st, int ds) {
        super(context, st, ds);
        mContext = context;
        mLockPatternUtils = new LockPatternUtils(mContext);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        mLogin = (EditText) findViewById(R.id.login);
        mLogin.setFilters(new InputFilter[] { new LoginFilter.UsernameFilterGeneric() } );
        mPassword = (EditText) findViewById(R.id.password);

        mOk = (Button) findViewById(R.id.ok);
        mOk.setOnClickListener(this);

        reset();
    }

    @Override
    protected boolean onRequestFocusInDescendants(int direction,
            Rect previouslyFocusedRect) {
        // send focus to the login field
        return mLogin.requestFocus(direction, previouslyFocusedRect);
    }

    public boolean needsInput() {
        return true;
    }

    public void setOnNotifyAccountResetCb(OnNotifyAccountReset callback) {
        this.mNotifyAccountResetCb = callback;
    }

    public void clearFocusOnInput() {
        mLogin.clearFocus();
        mPassword.clearFocus();

        // hide keyboard
        final InputMethodManager imm = (InputMethodManager)
                mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(mLogin.getWindowToken(), 0);
        imm.hideSoftInputFromWindow(mPassword.getWindowToken(), 0);
    }

    public void reset() {
        mLogin.setText("");
        mPassword.setText("");
        mLogin.requestFocus();
    }

    /** {@inheritDoc} */
    public void cleanUp() {
        if (mCheckingDialog != null) {
            mCheckingDialog.hide();
        }
    }

    public void onClick(View v) {
        if (v == mOk) {
            asyncCheckPassword();
        }
    }

    private void postOnCheckPasswordResult(final boolean success) {
        // ensure this runs on UI thread
        mLogin.post(new Runnable() {
            public void run() {
                if (success) {

                    Activity baseActivity = (Activity) mContext;

                    if (!baseActivity.isFinishing()) {
                        // Remove pattern
                        SharedPreferences prefs = PreferenceManager
                                .getDefaultSharedPreferences(mContext);
                        SharedPreferences.Editor editor = prefs.edit();
                        editor.remove(LockPatternActivity.PATTERN_LOCK_PROTECTED_APPS);
                        editor.commit();

                        if (mNotifyAccountResetCb != null) {
                            mNotifyAccountResetCb.onNotifyAccountReset();
                        } else {
                            baseActivity.setResult(Activity.RESULT_OK);
                            baseActivity.finish();
                        }
                    }
                } else {
                    Toast.makeText(mContext,
                            getResources().getString(
                                    R.string.pa_login_incorrect_login),
                            Toast.LENGTH_SHORT).show();
                    mPassword.setText("");
                }
            }
        });
    }

    /**
     * Given the string the user entered in the 'username' field, find
     * the stored account that they probably intended.  Prefer, in order:
     *
     *   - an exact match for what was typed, or
     *   - a case-insensitive match for what was typed, or
     *   - if they didn't include a domain, an exact match of the username, or
     *   - if they didn't include a domain, a case-insensitive
     *     match of the username.
     *
     * If there is a tie for the best match, choose neither --
     * the user needs to be more specific.
     *
     * @return an account name from the database, or null if we can't
     * find a single best match.
     */
    private Account findIntendedAccount(String username) {
        Account[] accounts = AccountManager.get(mContext).getAccountsByTypeAsUser("com.google",
                new UserHandle(ActivityManager.getCurrentUser()));

        // Try to figure out which account they meant if they
        // typed only the username (and not the domain), or got
        // the case wrong.

        Account bestAccount = null;
        int bestScore = 0;
        for (Account a: accounts) {
            int score = 0;
            if (username.equals(a.name)) {
                score = 4;
            } else if (username.equalsIgnoreCase(a.name)) {
                score = 3;
            } else if (username.indexOf('@') < 0) {
                int i = a.name.indexOf('@');
                if (i >= 0) {
                    String aUsername = a.name.substring(0, i);
                    if (username.equals(aUsername)) {
                        score = 2;
                    } else if (username.equalsIgnoreCase(aUsername)) {
                        score = 1;
                    }
                }
            }
            if (score > bestScore) {
                bestAccount = a;
                bestScore = score;
            } else if (score == bestScore) {
                bestAccount = null;
            }
        }
        return bestAccount;
    }

    private void asyncCheckPassword() {
        final String login = mLogin.getText().toString();
        final String password = mPassword.getText().toString();
        Account account = findIntendedAccount(login);
        if (account == null) {
            postOnCheckPasswordResult(false);
            return;
        }
        getProgressDialog().show();
        Bundle options = new Bundle();
        options.putString(AccountManager.KEY_PASSWORD, password);
        AccountManager.get(mContext).confirmCredentialsAsUser(account, options, null /* activity */,
                new AccountManagerCallback<Bundle>() {
            public void run(AccountManagerFuture<Bundle> future) {
                try {
                    final Bundle result = future.getResult();
                    final boolean verified = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
                    postOnCheckPasswordResult(verified);
                } catch (OperationCanceledException e) {
                    postOnCheckPasswordResult(false);
                } catch (IOException e) {
                    postOnCheckPasswordResult(false);
                } catch (AuthenticatorException e) {
                    postOnCheckPasswordResult(false);
                } finally {
                    mLogin.post(new Runnable() {
                        public void run() {
                            getProgressDialog().hide();
                        }
                    });
                }
            }
        }, null /* handler */, new UserHandle(ActivityManager.getCurrentUser()));
    }

    private Dialog getProgressDialog() {
        if (mCheckingDialog == null) {
            mCheckingDialog = new ProgressDialog(mContext);
            mCheckingDialog.setMessage(
                    mContext.getString(R.string.pa_login_checking_password));
            mCheckingDialog.setIndeterminate(true);
            mCheckingDialog.setCancelable(false);
            mCheckingDialog.getWindow().setType(
                    WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
        }
        return mCheckingDialog;
    }
}

