/*
 * Copyright (C) 2017 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 foo.bar.fill;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.app.assist.AssistStructure;
import android.app.assist.AssistStructure.WindowNode;
import android.app.assist.AssistStructure.ViewNode;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.os.CancellationSignal;
import android.service.autofill.AutofillService;
import android.service.autofill.Dataset;
import android.service.autofill.FillCallback;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
import android.service.autofill.SaveCallback;
import android.service.autofill.SaveInfo;
import android.service.autofill.SaveRequest;
import android.util.Log;
import android.view.View;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
import android.widget.EditText;
import android.widget.RemoteViews;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

import android.widget.TextView;
import foo.bar.fill.R;

public class FillService extends AutofillService {
    private static final String LOG_TAG = "FillService";

    static final boolean TEST_RESPONSE_AUTH = false;

    public static final String RESPONSE_ID = "RESPONSE_ID";

    static final String DATASET1_NAME = "Foo";
    static final String DATASET1_USERNAME = "Foo";
    static final String DATASET1_PASSWORD = "1";

    static final String DATASET2_NAME = "Bar";
    static final String DATASET2_USERNAME = "Bar";
    static final String DATASET2_PASSWORD = "12";

    static final String DATASET3_NAME = "Baz";
    static final String DATASET3_USERNAME = "Baz";
    static final String DATASET3_PASSWORD = "123";

    static final String DATASET4_NAME = "Bam";
    static final String DATASET4_USERNAME = "Bam";
    static final String DATASET4_PASSWORD = "1234";

    static final String DATASET5_NAME = "Bak";
    static final String DATASET5_USERNAME = "Bak";
    static final String DATASET5_PASSWORD = "12345";

    static final String EXTRA_RESPONSE_ID = "foo.bar.fill.extra.RESPONSE_ID";
    static final String EXTRA_DATASET_ID = "foo.bar.fill.extra.DATASET_ID";

    @Override
    public void onFillRequest(@NonNull FillRequest request,
            @NonNull CancellationSignal cancellationSignal,
            @NonNull FillCallback callback) {
        AssistStructure structure = request.getFillContexts().get(0).getStructure();

        dumpNodeTree(structure);

//        ViewNode username = findUsername(structure);
//        ViewNode password = findPassword(structure);

        ViewNode username = null;
        ViewNode password = null;
        final List<ViewNode> inputs = findTextInputs(structure);
        if (inputs.size() > 1) {
            username = inputs.get(0);
            password = inputs.get(1);
        }

        Log.i(LOG_TAG, "found username+username:" + (username != null && password != null));

        if (username != null && password != null) {
            final FillResponse response;

            if (TEST_RESPONSE_AUTH) {
                Intent intent = new Intent(this, AuthActivity.class);
                intent.putExtra(EXTRA_RESPONSE_ID, RESPONSE_ID);
                IntentSender sender = PendingIntent.getActivity(this, 0, intent,
                        PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT)
                        .getIntentSender();

                RemoteViews presentation = new RemoteViews(getPackageName(), R.layout.pathology);

//                presentation.setTextViewText(R.id.text1, "First");
//                Intent firstIntent = new Intent(this, FirstActivity.class);
//                presentation.setOnClickPendingIntent(R.id.text1, PendingIntent.getActivity(
//                        this, 0, firstIntent, PendingIntent.FLAG_CANCEL_CURRENT));

//                presentation.setTextViewText(R.id.text2, "Second");
//                Intent secondIntent = new Intent(this, SecondActivity.class);
//                presentation.setOnClickPendingIntent(R.id.text2, PendingIntent.getActivity(
//                        this, 0, secondIntent, PendingIntent.FLAG_CANCEL_CURRENT));

                response = new FillResponse.Builder()
                        .setAuthentication(new AutofillId[]{username.getAutofillId(),
                                password.getAutofillId()}, sender, presentation)
                        .build();
            } else {
                Intent intent = new Intent(this, AuthActivity.class);
                intent.putExtra(EXTRA_DATASET_ID, DATASET1_NAME);
                IntentSender sender = PendingIntent.getActivity(this, 0, intent,
                        PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT)
                        .getIntentSender();

                RemoteViews presentation1 = new RemoteViews(getPackageName(), R.layout.list_item);
                presentation1.setTextViewText(R.id.text1, DATASET1_NAME);

                RemoteViews presentation2 = new RemoteViews(getPackageName(), R.layout.list_item);
                presentation2.setTextViewText(R.id.text1, DATASET2_NAME);

                RemoteViews presentation3 = new RemoteViews(getPackageName(), R.layout.list_item);
                presentation3.setTextViewText(R.id.text1, DATASET3_NAME);

                RemoteViews presentation4 = new RemoteViews(getPackageName(), R.layout.list_item);
                presentation4.setTextViewText(R.id.text1, DATASET4_NAME);

                RemoteViews presentation5 = new RemoteViews(getPackageName(), R.layout.list_item);
                presentation5.setTextViewText(R.id.text1, /*DATASET5_NAME*/ "Auth needed");

                response = new FillResponse.Builder()
                        .addDataset(new Dataset.Builder(presentation1)
                                .setValue(username.getAutofillId(),
                                        AutofillValue.forText(DATASET1_USERNAME))
                                .setValue(password.getAutofillId(),
                                        AutofillValue.forText(DATASET1_PASSWORD))
                                .build())
//                        .addDataset(new Dataset.Builder(presentation2)
//                                .setValue(username.getAutofillId(),
//                                        AutofillValue.forText(DATASET2_USERNAME))
//                                .setValue(password.getAutofillId(),
//                                        AutofillValue.forText(DATASET2_PASSWORD))
////                                .setAuthentication(sender)
//                                .build())
//                        .addDataset(new Dataset.Builder(presentation3)
//                                .setValue(username.getAutofillId(),
//                                        AutofillValue.forText(DATASET3_USERNAME))
//                                .setValue(password.getAutofillId(),
//                                        AutofillValue.forText(DATASET3_PASSWORD))
////                                .setAuthentication(sender)
//                                .build())
//                        .addDataset(new Dataset.Builder(presentation4)
//                                .setValue(username.getAutofillId(),
//                                        AutofillValue.forText(DATASET4_USERNAME))
//                                .setValue(password.getAutofillId(),
//                                        AutofillValue.forText(DATASET4_PASSWORD))
////                                .setAuthentication(sender)
//                                .build())
//                        .addDataset(new Dataset.Builder(presentation5)
//                                .setValue(username.getAutofillId(),
//                                        AutofillValue.forText(DATASET5_USERNAME))
//                                .setValue(password.getAutofillId(),
//                                        AutofillValue.forText(DATASET5_PASSWORD))
//                                .setAuthentication(sender)
//                                .build())
                        .setSaveInfo(new SaveInfo.Builder(
                                SaveInfo.SAVE_DATA_TYPE_PASSWORD
                                        | SaveInfo.SAVE_DATA_TYPE_USERNAME,
                                new AutofillId[] {username.getAutofillId(),
                                        password.getAutofillId()})
                                .setFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
                                .build())
                        .build();
            }

            callback.onSuccess(response);
        } else {
            //callback.onFailure("Whoops");
            callback.onSuccess(null);
        }
    }

    @Override
    public void onSaveRequest(@NonNull SaveRequest request, @NonNull SaveCallback callback) {
        AssistStructure structure = request.getFillContexts().get(0).getStructure();
        ViewNode username = findUsername(structure);
        ViewNode password = findPassword(structure);
    }

    static void dumpNodeTree(AssistStructure structure) {
        findByPredicate(structure, (node) -> {
            if (node.getAutofillValue() != null) {
                Log.e("class:" + LOG_TAG, node.getClassName() + " value:" + node.getAutofillValue());
            }
//            Log.e(LOG_TAG, (node.getAutofillValue() != null && node.getAutofillValue().isText())
//                    ? node.getAutofillValue().getTextValue().toString() + "-" +node.getAutofillId() : "NOPE");
            return false;
        });
    }

    List<ViewNode > findTextInputs(AssistStructure structure) {
        final List<ViewNode> inputs = new ArrayList<>();
        findByPredicate(structure, (node) -> {
            if (node.getClassName().equals(EditText.class.getName())) {
                inputs.add(node);
            }
            return false;
        });
        return inputs;
    }

    static ViewNode findUsername(AssistStructure structure) {
        return findByPredicate(structure, (node) ->
            node.getAutofillType() == View.AUTOFILL_TYPE_TEXT
                    && (autofillTextValueContains(node, "username")
                            || "username".equals(node.getIdEntry()))
        );
    }

    static ViewNode findPassword(AssistStructure structure) {
        return findByPredicate(structure, (node) ->
            node.getAutofillType() == View.AUTOFILL_TYPE_TEXT
                    && (autofillTextValueContains(node, "password")
                            || "password".equals(node.getIdEntry()))
        );
    }

    private static boolean autofillTextValueContains(ViewNode node, String text) {
        return node.getAutofillValue() != null
                && node.getAutofillValue().getTextValue() != null
                && node.getAutofillValue().getTextValue().toString().toLowerCase()
                .contains(text.toLowerCase());
    }

    private static ViewNode findByPredicate(AssistStructure structure,
            Predicate<ViewNode> predicate) {
        final int windowCount = structure.getWindowNodeCount();
        for (int i = 0; i < windowCount; i++) {
            WindowNode window = structure.getWindowNodeAt(i);
            ViewNode root = window.getRootViewNode();
            if (root == null) {
                return null;
            }
            ViewNode node = findByPredicate(root, predicate);
            if (node != null) {
                return node;
            }
        }
        return null;
    }

    private static ViewNode findByPredicate(ViewNode root, Predicate<ViewNode> predicate) {
        if (root == null) {
            return null;
        }
        if (predicate.test(root)) {
            return root;
        }
        final int childCount = root.getChildCount();
        for (int i = 0; i < childCount; i++) {
            ViewNode child = root.getChildAt(i);
            ViewNode node = findByPredicate(child, predicate);
            if (node != null) {
                return node;
            }
        }
        return null;
    }
}
