/*
 * Copyright (c) 2020. BoostTag E.I.R.L. Romell D.Z.
 * All rights reserved
 * porfile.romellfudi.com
 */

package com.romellfudi.ussdlibrary;

import android.accessibilityservice.AccessibilityService;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Parcel;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;

import androidx.annotation.RequiresApi;

import com.blankj.utilcode.util.SPUtils;
import com.elvishew.xlog.XLog;
import com.google.gson.Gson;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * AccessibilityService for ussd windows on Android mobile Telcom
 *
 * @author Romell Dominguez
 * @version 1.1.c 27/09/2018
 * @since 1.0.a
 */
public class USSDService extends AccessibilityService {

    private static final String TAG = "USSDServiceUSSD";

    public static AccessibilityEvent event;

    @RequiresApi(api = Build.VERSION_CODES.P)
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            USSDService.event = new AccessibilityEvent(event);
        } else {
            Parcel parcel = Parcel.obtain();
            event.writeToParcel(parcel, 0);
            parcel.setDataPosition(0);
            AccessibilityEvent newEvent = AccessibilityEvent.CREATOR.createFromParcel(parcel);
            parcel.recycle();
            USSDService.event = newEvent;
        }

        new Handler(Looper.getMainLooper()).postDelayed(() -> {
            Log.d("hzshkj", String.format("USSDService onAccessibilityEvent: [type] %s [class] %s [package] %s [time] %s [text] %s [ContentChangeTypes] %s [WindowChanges] %s",
                    event.getEventType(),
                    event.getClassName(),
                    event.getPackageName(),
                    event.getEventTime(),
                    event.getText(),
                    event.getContentChangeTypes(),
                    event.getWindowChanges()
            ));
            XLog.i(String.format("USSDService onAccessibilityEvent: [type] %s [class] %s [package] %s [time] %s [text] %s [ContentChangeTypes] %s [WindowChanges] %s",
                    event.getEventType(),
                    event.getClassName(),
                    event.getPackageName(),
                    event.getEventTime(),
                    event.getText(),
                    event.getContentChangeTypes(),
                    event.getWindowChanges()
            ));

            if (USSDController.instance == null || !USSDController.instance.isRunning) {
                XLog.e("USSDService Error : USSDController == " + (USSDController.instance == null));
                XLog.e("USSDService Error : USSDController.isRunning == " + (USSDController.instance.isRunning));
//            return;
            }

            String response = event.getText().toString();
            XLog.i("USSDService response = " + response);

            if (LoginView(event) && notInputText(event)) {
                XLog.i("USSDService: 1");
                clickOnButton(event, 0);
                USSDController.instance.isRunning = false;
                if (USSDController.instance.send)
                    USSDController.instance.callbackMessage.over(response);
                else
                    USSDController.instance.callbackInvoke.over(response);
            } else if (problemView(event) || LoginView(event)) {
                XLog.i("USSDService: 2");
                clickOnButton(event, 1);
                if (USSDController.instance.send)
                    USSDController.instance.callbackMessage.over(response);
                else
                    USSDController.instance.callbackInvoke.over(response);

            } else if (isUSSDWidget(event)) {

                XLog.i("USSDService: 3");
                if (notInputText(event)) {
                    XLog.i("USSDService: 4");
//                    clickOnButton(event, 0);
                    USSDController.instance.isRunning = false;
                    if (USSDController.instance.send)
                        USSDController.instance.callbackMessage.over(response);
                    else
                        USSDController.instance.callbackInvoke.over(response);
                } else {
                    XLog.i("USSDService: 5");
                    if (USSDController.instance.send)
                        USSDController.instance.callbackMessage.responseMessage(response);
                    else
                        USSDController.instance.callbackInvoke.responseInvoke(response);
                }
            } else {
                XLog.e("USSDService Error : 未走进任何逻辑循环");
            }
        }, 2000); // 延迟 1 秒

    }

    /**
     * Send whatever you want via USSD
     *
     * @param text any string
     */
    public static void send(String text) {
        setTextIntoField(event, text);
        clickOnButton(event, 1);
    }

    /**
     * Cancel USSD
     */
    public static void cancel() {
        clickOnButton(event, 0);
    }


    /**
     * set text into input text at USSD widget
     *
     * @param event AccessibilityEvent
     * @param data  Any String
     */
    private static void setTextIntoField(AccessibilityEvent event, String data) {
        USSDController ussdController = USSDController.instance;
        Bundle arguments = new Bundle();
        arguments.putCharSequence(
                AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, data);

        for (AccessibilityNodeInfo leaf : getLeaves(event)) {

            if (leaf.getClassName().equals("android.widget.EditText")
                    && !leaf.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments)) {
                ClipboardManager clipboardManager = ((ClipboardManager) ussdController.context
                        .getSystemService(Context.CLIPBOARD_SERVICE));
                if (clipboardManager != null) {
                    clipboardManager.setPrimaryClip(ClipData.newPlainText("text", data));
                }

                leaf.performAction(AccessibilityNodeInfo.ACTION_PASTE);
            }
        }
    }

    /**
     * Method evaluate if USSD widget has input text
     *
     * @param event AccessibilityEvent
     * @return boolean has or not input text
     */
    protected static boolean notInputText(AccessibilityEvent event) {
        boolean flag = true;
        XLog.i("USSDService Event Json event.source = : " + event.getSource());
        for (AccessibilityNodeInfo leaf : getLeaves(event)) {
            if (leaf.getClassName().equals("android.widget.EditText")) {
                flag = false;
            }
        }
        return flag;
    }

    /**
     * The AccessibilityEvent is instance of USSD Widget class
     *
     * @param event AccessibilityEvent
     * @return boolean AccessibilityEvent is USSD
     */
    private boolean isUSSDWidget(AccessibilityEvent event) {
        return (event.getClassName().equals("amigo.app.AmigoAlertDialog")
                || event.getClassName().equals("android.app.AlertDialog")
                || event.getClassName().equals("com.android.phone.oppo.settings.LocalAlertDialog")
                || event.getClassName().equals("com.zte.mifavor.widget.AlertDialog")
                || event.getClassName().equals("color.support.v7.app.AlertDialog")
                || event.getClassName().equals("com.transsion.widgetslib.dialog.PromptDialog")
                || event.getClassName().equals("miuix.appcompat.app.AlertDialog")
                || event.getClassName().equals("androidx.appcompat.app.AlertDialog"));
    }

    /**
     * The View has a login message into USSD Widget
     *
     * @param event AccessibilityEvent
     * @return boolean USSD Widget has login message
     */
    private boolean LoginView(AccessibilityEvent event) {
        return isUSSDWidget(event)
                && USSDController.instance.map.get(USSDController.KEY_LOGIN)
                .contains(event.getText().get(0).toString());
    }

    /**
     * The View has a problem message into USSD Widget
     *
     * @param event AccessibilityEvent
     * @return boolean USSD Widget has problem message
     */
    protected boolean problemView(AccessibilityEvent event) {
        return isUSSDWidget(event)
                && USSDController.instance.map.get(USSDController.KEY_ERROR)
                .contains(event.getText().get(0).toString());
    }

    /**
     * click a button using the index
     *
     * @param event AccessibilityEvent
     * @param index button's index
     */
    protected static void clickOnButton(AccessibilityEvent event, int index) {
        int count = -1;
        for (AccessibilityNodeInfo leaf : getLeaves(event)) {
            if (leaf.getClassName().toString().toLowerCase().contains("button")) {
                count++;
                if (count == index) {
                    leaf.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                }
            }
        }
    }

    private static List<AccessibilityNodeInfo> getLeaves(AccessibilityEvent event) {
        List<AccessibilityNodeInfo> leaves = new ArrayList<>();
        if (event.getSource() != null) {
            getLeaves(leaves, event.getSource());
        }

        return leaves;
    }

    private static void getLeaves(List<AccessibilityNodeInfo> leaves, AccessibilityNodeInfo node) {
        if (node.getChildCount() == 0) {
            leaves.add(node);
            return;
        }

        for (int i = 0; i < node.getChildCount(); i++) {
            getLeaves(leaves, node.getChild(i));
        }
    }

    /**
     * Active when SO interrupt the application
     */
    @Override
    public void onInterrupt() {
        Log.d(TAG, "onInterrupt");
    }

    /**
     * Configure accessibility server from Android Operative System
     */
    @Override
    protected void onServiceConnected() {
        super.onServiceConnected();
        Log.d(TAG, "onServiceConnected");
    }

    public static String getEventNowRootInActiveWindow(AccessibilityEvent event) {
        String PageJson = "{}";
        AccessibilityNodeInfo node = event.getSource();
        if (node != null) {
            NodeInfoWrapper nodeInfoWrapper = traverseNode(node);
            Gson gson = new Gson();
            PageJson = gson.toJson(nodeInfoWrapper);
        } else {
            PageJson = PayController.getNowRootInActiveWindow();
        }
        return PageJson;
    }

    private static class NodeInfoWrapper {
        public String txt;
        public String vId;
        public String cName;
        public String pName;
        public int hashcode;
        public int childCount;
        //用于判断节点是否可以打开弹出窗口（popup）canOpenPopup()方法用于判断给定的节点是否具有打开弹出窗口的能力
        public boolean canOpenPopup;
        //判断节点是否对用户可见
        public boolean visibleToUser;
        //判断节点是否启用
        public boolean isEnabled;
        public String hintText;
        //判断节点是否可点击
        public boolean isClickable;
        //判断节点是否可长按
        public boolean isLongClickable;
        //判断节点是否可编辑
        public boolean isEditable;
        //判断节点是否可选择
        public boolean isCheckable;
        //判断节点的选择状态
        public boolean isChecked;
        //判断节点是否具有焦点
        public boolean focused;
        //判断节点是否可滚动
        public boolean scrollable;

        public boolean isSelected;

        public boolean isAccessibilityFocused;
        public boolean isContentInvalid;
        public boolean isContextClickable;
        public boolean isDismissable;
        public boolean isFocusable;
        public boolean isFocused;
        public boolean isHeading;
        public boolean isImportantForAccessibility;
        public boolean isMultiLine;
        public boolean isPassword;
        public boolean isScreenReaderFocusable;
        public boolean isScrollable;
        public boolean isShowingHintText;
        public boolean isTextEntryKey;
        public boolean isTextSelectable;
        public boolean isVisibleToUser;
        public List<NodeInfoWrapper> childNodes;
    }


    private static NodeInfoWrapper traverseNode(AccessibilityNodeInfo nodeInfo) {
        NodeInfoWrapper nodeInfoWrapper = new NodeInfoWrapper();
        nodeInfoWrapper.txt = nodeInfo.getText() != null ? nodeInfo.getText().toString() : "";
        nodeInfoWrapper.vId = nodeInfo.getViewIdResourceName();
        nodeInfoWrapper.cName = nodeInfo.getClassName().toString();
        nodeInfoWrapper.pName = nodeInfo.getPackageName().toString();
        nodeInfoWrapper.hashcode = nodeInfo.hashCode();
        nodeInfoWrapper.childCount = nodeInfo.getChildCount();
        nodeInfoWrapper.canOpenPopup = nodeInfo.canOpenPopup();
        nodeInfoWrapper.visibleToUser = nodeInfo.isVisibleToUser();
        nodeInfoWrapper.isEnabled = nodeInfo.isEnabled();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            nodeInfoWrapper.hintText = nodeInfo.getHintText() != null ? nodeInfo.getText().toString() : "";
        }
        nodeInfoWrapper.isClickable = nodeInfo.isClickable();
        nodeInfoWrapper.isLongClickable = nodeInfo.isLongClickable();
        nodeInfoWrapper.isEditable = nodeInfo.isEditable();
        nodeInfoWrapper.isCheckable = nodeInfo.isCheckable();
        nodeInfoWrapper.isChecked = nodeInfo.isChecked();
        nodeInfoWrapper.focused = nodeInfo.isFocused();
        nodeInfoWrapper.scrollable = nodeInfo.isScrollable();
        nodeInfoWrapper.isSelected = nodeInfo.isSelected();
        nodeInfoWrapper.isAccessibilityFocused = nodeInfo.isAccessibilityFocused();
        nodeInfoWrapper.isContentInvalid = nodeInfo.isContentInvalid();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            nodeInfoWrapper.isContextClickable = nodeInfo.isContextClickable();
        }
        nodeInfoWrapper.isDismissable = nodeInfo.isDismissable();
        nodeInfoWrapper.isFocusable = nodeInfo.isFocusable();
        nodeInfoWrapper.isFocused = nodeInfo.isFocused();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            nodeInfoWrapper.isHeading = nodeInfo.isHeading();
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            nodeInfoWrapper.isImportantForAccessibility = nodeInfo.isImportantForAccessibility();
        }
        nodeInfoWrapper.isMultiLine = nodeInfo.isMultiLine();
        nodeInfoWrapper.isPassword = nodeInfo.isPassword();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            nodeInfoWrapper.isScreenReaderFocusable = nodeInfo.isScreenReaderFocusable();
        }
        nodeInfoWrapper.isScrollable = nodeInfo.isScrollable();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            nodeInfoWrapper.isShowingHintText = nodeInfo.isShowingHintText();
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            nodeInfoWrapper.isTextEntryKey = nodeInfo.isTextEntryKey();
        }
        nodeInfoWrapper.isVisibleToUser = nodeInfo.isVisibleToUser();


        List<NodeInfoWrapper> childNodes = new ArrayList<>();
        for (int i = 0; i < nodeInfo.getChildCount(); i++) {
            AccessibilityNodeInfo childNode = nodeInfo.getChild(i);
            if (childNode != null) {
                NodeInfoWrapper childNodeWrapper = traverseNode(childNode);
                childNodes.add(childNodeWrapper);
            }
        }
        nodeInfoWrapper.childNodes = childNodes;

        return nodeInfoWrapper;
    }
}
