#pylint: disable=E1101
#pylint: disable=E0611

import AppKit
import collections
import error
import Foundation
import log
import os
import re
import sys
import objc
import process
import psutil
import Quartz
from Quartz import CG
from Quartz import CoreGraphics
import rumps
import struct
import time
import subprocess
import threading
import traceback

all_windows = None
menu_is_open = False

NO_APP = {
   "NSApplicationName": "",
   "NSApplicationBundleIdentifier": "",
   "NSApplicationProcessIdentifier": -1,
}

def set_menu_open(value):
    global menu_is_open
    menu_is_open = value

def is_menu_open():
    return menu_is_open

def get_current_app():
    return AppKit.NSWorkspace.sharedWorkspace().activeApplication() or NO_APP

def get_current_app_name():
    return get_current_app()["NSApplicationName"]

def get_current_app_short_name():
    name = get_current_app().get("NSApplicationBundleIdentifier", "???.%s" % get_current_app_name()).split(".")[-1]
    return name[0].capitalize() + name[1:]

def get_current_app_pid():
    return get_current_app()["NSApplicationProcessIdentifier"]

def get_active_chrome_tabs():
    return [window for window in get_all_windows() if is_chrome_window(window)]

def get_active_window_name():
    return get_window_name(get_current_app_pid())

def dark_mode():
    return os.popen("defaults read -g AppleInterfaceStyle 2>/dev/null").read()

def get_active_window_dimensions():
    return get_window_dimensions(get_current_app_pid())

def get_screen_pixel(x, y):
    image = CG.CGWindowListCreateImage(
        CoreGraphics.CGRectMake(x, y, 2, 2),
        CG.kCGWindowListOptionOnScreenOnly,
        CG.kCGNullWindowID,
        CG.kCGWindowImageDefault)
    bytes = CG.CGDataProviderCopyData(CG.CGImageGetDataProvider(image))
    b, g, r, a = struct.unpack_from("BBBB", bytes, offset=0)
    return (r, g, b, a)

def is_chrome_window(window):
    return is_active_window(window) and window.valueForKey_('kCGWindowOwnerName') == "Google Chrome"

def is_active_window(window, pid=None):
    if pid and window.valueForKey_('kCGWindowOwnerPID') != pid:
        return False
    return window.valueForKey_('kCGWindowIsOnscreen') and window.valueForKey_('kCGWindowName')

def get_window_name(pid):
    windows = [window for window in get_all_windows() if is_active_window(window, pid)]
    return windows and windows[0].get('kCGWindowName', '') or ''

def get_window_dimensions(pid):
    windows = [window for window in get_all_windows() if is_active_window(window, pid)]
    if windows:
        bounds = windows[0].get('kCGWindowBounds')
        return (bounds['X'], bounds['Y'], bounds['Width'], bounds['Height'])
    return (0,0,0,0)

def clear_windows_cache():
    global all_windows
    all_windows = None

def get_all_windows():
    global all_windows
    if not all_windows:
        all_windows = Quartz.CGWindowListCopyWindowInfo(Quartz.kCGWindowListExcludeDesktopElements, Quartz.kCGNullWindowID)
    for window in all_windows:
        if False and window.valueForKey_('kCGWindowIsOnscreen') :
            print(window)
    return all_windows

def run_osa_script(script):
    os.system("osascript -e '%s' &" % script)

def get_auto_release_pool():
    return Quartz.NSAutoreleasePool.alloc().init()

def on_ethernet():
    try:
        interface = get_line(['route', '-n', 'get', 'default'], ["interface"]).split(' ')[-1]
        # MacOS likes to enumerate adapters if it has seen the same model before.
        # For instance, `Ethernet` the first time and then `Ethernet 1` afterwards.
        device = get_line(
            ['networksetup', 'listnetworkserviceorder'],
            [r'ethernet(,|\s\d+,)', r'lan(,|\s\d+,)', r'ethernet adapter(,|\s\d+,)', r'ethernet slot\s\d+,']
        ).split(' ')[-1]
        return interface in device
    except:
        return False

def get_line(command, regexes):
    output = subprocess.check_output(command)
    lines = output.split('\n')
    try:
        return list(filter(lambda line: any(re.search(regex, line, re.IGNORECASE) for regex in regexes), lines))[0]
    except Exception as e:
        print("Cannot find regexes in output", e)
        return

class OnMainThread():
    def initWithCallback_(self, callback):
        self.callback = callback
        return self

    @objc.namedselector("run_:")
    def run_(self, args=None):
        self.callback()

    def run(self):
        try:
            self.pyobjc_performSelectorOnMainThread_withObject_("run_:", None)
        except Exception as e:
            print(e)
            rumps.quit_application()


class Timer(threading.Thread):
    def __init__(self, interval, callback):
        global OnMainThread
        super(Timer, self).__init__(name="Timer for %ds for %s" % (interval, callback))
        OnMainThread = type('OnMainThread', (Foundation.NSObject,), dict(OnMainThread.__dict__))
        self.callback = OnMainThread.alloc().initWithCallback_(callback)
        self.interval = interval

    def run(self):
        while True:
            time.sleep(self.interval)
            try:
                self.callback.run()
            except psutil.NoSuchProcess:
                pass # this is normal
            except Exception as e:
                error.error("Error in Timer callback '%s': %s" % (self.callback.callback.im_func.__name__, e))

image_cache = {}
rumps_nsimage_from_file = rumps.rumps._nsimage_from_file

def _nsimage_from_file(path, dimensions=None, template=None):
    if path in image_cache:
        return image_cache[path]
    else:
        image = rumps_nsimage_from_file(path, None, None)
        image_cache[path] = image
        return image

rumps.rumps._nsimage_from_file = _nsimage_from_file