package info.knacki.pass.services;

import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.support.annotation.RequiresApi;
import android.view.Gravity;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Toast;

import java.io.File;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;

import info.knacki.gitdroid.callback.OnResponseListener;
import info.knacki.pass.R;
import info.knacki.pass.io.FileInterfaceFactory;
import info.knacki.pass.ui.passwordPicker.PasswordPickerFactory;

@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class AccessibilityService extends android.accessibilityservice.AccessibilityService {
    private static final Logger log = Logger.getLogger(AccessibilityService.class.getName());
    private final static long CANCEL_DELAY_SEC = 10;
    private final int NOTIFICATION_ID = 1;
    private static AccessibilityService fInstance;
    private boolean fManagingEvent;

    private class LastCancellation {
        public long lastCancelledTime;
        public String lastCancelledPackage;

        private LastCancellation() {
            Clear();
        }

        public void Cancel(AccessibilityNodeInfo source) {
            fLastCancellation.lastCancelledPackage = source.getPackageName().toString();
            fLastCancellation.lastCancelledTime = new Date().getTime() / 1000;
        }

        public boolean IsCancelling(AccessibilityNodeInfo source, boolean clearCancelling) {
            boolean cancelling = lastCancelledPackage != null && source.getPackageName().toString().equals(lastCancelledPackage) && (new Date().getTime() / 1000) -lastCancelledTime <= CANCEL_DELAY_SEC;
            if (cancelling && clearCancelling)
                this.Clear();
            return cancelling;
        }

        public void Clear() {
            this.lastCancelledPackage = null;
            this.lastCancelledTime = 0;
        }
    }
    protected LastCancellation fLastCancellation = new LastCancellation();

    @Override
    protected void onServiceConnected() {
        log.info("Service started");
        super.onServiceConnected();
        fInstance = this;
        fManagingEvent = false;
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        log.severe("BOATS !");
        synchronized (AccessibilityService.class) {
            if (fManagingEvent || fLastCancellation.IsCancelling(event.getSource(), true))
                return;
            if (event.isPassword())
                fManagingEvent = DisplayPasswordList(event.getSource());
        }
    }

    private boolean DisplayPasswordList(final AccessibilityNodeInfo source) {
        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.MATCH_PARENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_PHONE,
                0,
                PixelFormat.TRANSLUCENT);
        params.gravity = Gravity.START | Gravity.BOTTOM;

        final AccessibilityView openWindow = new AccessibilityView(this);
        openWindow.Init(this, new AccessibilityView.AccessibilityViewListener() {
                    @Override
                    public void OnPasswordClicked(File f) {
                        new Handler(getMainLooper()).post(() -> {
                            LoadFile(f, openWindow.getWindowToken(), source, () ->
                                CloseOpenWindow(openWindow)
                            );
                        });
                    }

                    @Override
                    public void cancel() {
                        new Handler(getMainLooper()).post(() -> {
                            fLastCancellation.Cancel(source);
                            CloseOpenWindow(openWindow);
                            fManagingEvent = false;
                            SetFocus(source);
                        });
                    }
                })
                .AddCancelButton();

        try {
            WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
            wm.addView(openWindow, params);
            return true;
        }
        catch (WindowManager.BadTokenException e) {
            Toast.makeText(this, getResources().getString(R.string.app_name) +": " +getResources().getString(R.string.unauthorized_draw_over), Toast.LENGTH_LONG).show();
        }
        return false;
    }

    private void CloseOpenWindow(AccessibilityView openWindow) {
        if (openWindow != null) {
            WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
            wm.removeViewImmediate(openWindow);
        }
    }

    private void LoadFile(File f, IBinder windowToken, AccessibilityNodeInfo source, Runnable done) {
        fManagingEvent = false;
        log.info("Loading " +f.getName());
        FileInterfaceFactory.GetFileInterface(this, PasswordPickerFactory.GetPasswordPicker(this, windowToken), f).ReadFile(new OnResponseListener<String>() {
            @Override
            public void OnResponse(String result) {
                done.run();
                SendPassword(source, result);
            }

            @Override
            public void OnError(String msg, Throwable e) {
                done.run();
                Toast.makeText(AccessibilityService.this, msg, Toast.LENGTH_LONG).show();
                log.log(Level.SEVERE, msg, e);
            }
        });
    }

    private void SendPassword(AccessibilityNodeInfo accessibilityNode, String password) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Bundle passContent = new Bundle();
            passContent.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, password);
            try {
                accessibilityNode.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, passContent);
            } catch (Throwable e) {
                LegacySendPassword(accessibilityNode, password);
            }
        } else {
            LegacySendPassword(accessibilityNode, password);
        }
        fManagingEvent = false;
    }

    private void SetFocus(AccessibilityNodeInfo source) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            source.performAction(AccessibilityNodeInfo.ACTION_CLICK);
        }
    }

    private void LegacySendPassword(AccessibilityNodeInfo accessibilityNode, String password) {
        final ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
        final ClipData passClip = ClipData.newPlainText("password", password);
        final ClipData prev = clipboard.getPrimaryClip();

        clipboard.setPrimaryClip(passClip);
        accessibilityNode.performAction(AccessibilityNodeInfo.ACTION_PASTE);
        clipboard.setPrimaryClip(prev == null ? ClipData.newPlainText("", "") : prev);
    }

    @Override
    public boolean onUnbind(Intent intent) {
        log.info("Accessibility service unbinded");
        fInstance = null;
        return super.onUnbind(intent);
    }

    @Override
    public void onInterrupt() {
    }

    public static boolean IsRunning() {
        return fInstance != null;
    }

    public static boolean IsEnabled(Context ctx) {
        final AccessibilityManager accessibilityManager = (AccessibilityManager) ctx.getSystemService(ACCESSIBILITY_SERVICE);
        final String myAppProcessName = ctx.getApplicationInfo().processName;

        for (AccessibilityServiceInfo i: accessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK)) {
            ResolveInfo resolveInfo = i.getResolveInfo();
            if (resolveInfo == null ||
                    resolveInfo.serviceInfo == null ||
                    resolveInfo.serviceInfo.applicationInfo == null)
                continue;

            if (myAppProcessName.equals(i.getResolveInfo().serviceInfo.applicationInfo.processName)) {
                if (!IsRunning())
                    log.warning("Service enabled but not running");
                return true;
            }
        }
        return false;
    }
}
