import os
import logging.config
from random import randint
import zlib
import struct
import socket
import time

from PIL import Image

import config
# from config import ADB_ROOT, ADB_HOST, SCREEN_SHOOT_SAVE_PATH, ShellColor, CONFIG_PATH,enable_adb_host_auto_detect, ADB_SERVER
from .ADBClientSession import ADBClientSession
from util.socketutil import recvall
from . import revconn

# from numpy import average, dot, linalg

logger = logging.getLogger(__name__)


def _screencap_to_image(cap):
    w, h, pixels = cap
    return Image.frombytes('RGBA', (w, h), pixels)


def _ensure_pil_image(imgorfile):
    if isinstance(imgorfile, Image.Image):
        return imgorfile
    return Image.open(imgorfile)


def check_adb_alive():
    try:
        sess = ADBClientSession(config.ADB_SERVER)
        version = int(sess.service('host:version').read_response().decode(), 16)
        logger.debug('ADB server version %d', version)
        return True
    except ConnectionRefusedError:
        return False
    except RuntimeError:
        return False


def ensure_adb_alive():
    if check_adb_alive():
        return
    logger.info('尝试启动 adb server')
    import subprocess
    adbbin = config.get('device/adb_binary', None)
    if adbbin is None:
        adb_binaries = ['adb', os.path.join(config.ADB_ROOT, 'adb')]
    else:
        adb_binaries = [adbbin]
    for adbbin in adb_binaries:
        try:
            logger.debug('trying %r', adbbin)
            subprocess.run([adbbin, 'start-server'], check=True)
            return True
        except FileNotFoundError:
            pass
        except subprocess.CalledProcessError:
            pass
    raise OSError("can't start adb server")


class ADBConnector:
    def __init__(self, adb_serial=None):
        # os.chdir(ADB_ROOT)
        self.ADB_ROOT = config.ADB_ROOT
        self.adb_serial = adb_serial
        self.host_session_factory = lambda: ADBClientSession(config.ADB_SERVER)
        self.rch = None
        if self.adb_serial is None:
            self.adb_serial = self.__adb_device_name_detector()
        self.device_session_factory = lambda: self.host_session_factory().device(self.adb_serial)
        self.cache_screenshot = config.get('device/cache_screenshot', True)
        self.last_screenshot_timestamp = 0
        self.last_screenshot_duration = 0
        self.last_screenshot = None

        if config.get('device/try_emulator_enhanced_mode', True):
            loopbacks = self._detect_loopbacks()
            if len(loopbacks):
                logger.debug('possible loopback addresses: %s', repr(loopbacks))
                self.rch = revconn.ReverseConnectionHost()
                self.rch.start()
                if self._test_reverse_connection(loopbacks):
                    logger.info('正在使用模拟器优化模式')
                    self.screencap = self._reverse_connection_screencap
                else:
                    self.rch.stop()
        else:
            self.loopback = None

    def __del__(self):
        if self.rch and self.rch.is_alive():
            self.rch.stop()

    def __adb_device_name_detector(self):
        devices = [x for x in self.host_session_factory().devices() if x[1] != 'offline']

        if len(devices) == 0:
            auto_connect = config.get('device/adb_auto_connect', None)
            if auto_connect is not None:
                logger.info('没有已连接设备，尝试连接 %s', auto_connect)
                try:
                    self.host_session_factory().disconnect(auto_connect)
                except:
                    pass
                self.host_session_factory().connect(auto_connect)
            else:
                raise RuntimeError('找不到可用设备')

        devices = [x for x in self.host_session_factory().devices() if x[1] != 'offline']

        always_use_device = config.get('device/adb_always_use_device', None)
        if always_use_device is not None:
            if always_use_device not in (x[0] for x in devices):
                raise RuntimeError('设备 %s 未连接' % always_use_device)
            return always_use_device

        if len(devices) == 1:
            device_name = devices[0][0]
        elif len(devices) > 1:
            logger.info("检测到多台设备")
            num = 0
            while True:
                try:
                    num = int(input("请输入序号选择设备: "))
                    if not 0 <= num < len(devices):
                        raise ValueError()
                    break
                except ValueError:
                    logger.error("输入不合法，请重新输入")
            device_name = devices[num][0]
        else:
            raise RuntimeError('找不到可用设备')
        logger.info("确认设备名称：" + device_name)
        return device_name

    def run_device_cmd(self, cmd, DEBUG_LEVEL=2):
        output = self.device_session_factory().exec(cmd)
        logger.debug("command: %s", cmd)
        logger.debug("output: %s", repr(output))
        return output

    def get_sub_screen(self, image, screen_range):
        return image.crop(
            (
                screen_range[0][0],
                screen_range[0][1],
                screen_range[0][0] + screen_range[1][0],
                screen_range[0][1] + screen_range[1][1]
            )
        )


    def _detect_loopbacks(self):
        board = self.device_session_factory().exec('getprop ro.product.board')
        if b'goldfish' in board:
            return ['10.0.2.2']
        modules = self.device_session_factory().exec('grep -o vboxguest /proc/modules')
        if b'vboxguest' in modules:
            arp = self.device_session_factory().exec('cat /proc/net/arp')
            return [x[:x.find(b' ')].decode() for x in arp.splitlines()[1:]]
        return []

    def _test_reverse_connection(self, loopbacks):
        for addr in loopbacks:
            logger.debug('testing loopback address %s', addr)
            future = self.rch.register_cookie()
            with future:
                cmd = 'echo -n %sOKAY | nc -w 1 %s %d' % (future.cookie.decode(), addr, self.rch.port)
                logger.debug(cmd)
                control_sock = self.device_session_factory().exec_stream(cmd)
                with control_sock:
                    conn = future.get(2)
                    if conn is not None:
                        data = recvall(conn)
                        conn.close()
                        if data == b'OKAY':
                            self.loopback = addr
                            logger.debug('found loopback address %s', addr)
                            return True
        return False

    def screencap_png(self):
        """returns PNG bytes"""
        s = self.device_session_factory().exec_stream('screencap -p')
        data = recvall(s, 4194304)
        return data

    def screencap(self):
        """returns (width, height, pixels)
        pixels in RGBA/RGBX format"""
        s = self.device_session_factory().exec_stream('screencap|gzip -1')
        data = recvall(s, 4194304)
        s.close()
        data = zlib.decompress(data, zlib.MAX_WBITS | 16, 8388608)
        w, h, f = struct.unpack_from('III', data, 0)
        assert (f == 1)
        return (w, h, data[12:])

    def _reverse_connection_screencap(self):
        """returns (width, height, pixels)
        pixels in RGBA/RGBX format"""
        future = self.rch.register_cookie()
        with future:
            control_sock = self.device_session_factory().exec_stream('(echo -n %s; screencap) | nc %s %d' % (future.cookie.decode(), self.loopback, self.rch.port))
            with control_sock:
                with future.get() as conn:
                    data = recvall(conn, 8388608, True)
        w, h, f = struct.unpack_from('III', data, 0)
        assert (f == 1)
        return (w, h, data[12:].tobytes())

    def screenshot(self, cached=True):
        t0 = time.monotonic()
        if cached and self.cache_screenshot:
            if self.last_screenshot is not None and t0 - self.last_screenshot_timestamp < self.last_screenshot_duration:
                return self.last_screenshot
        rawcap = self.screencap()
        img = _screencap_to_image(rawcap)
        t1 = time.monotonic()
        self.last_screenshot_timestamp = t1
        self.last_screenshot_duration = t1 - t0
        self.last_screenshot = img
        return img

    def touch_swipe2(self, origin, movement, duration=None):
        # sleep(1)
        x1, y1, x2, y2 = origin[0], origin[1], origin[0] + movement[0], origin[1] + movement[1]

        logger.debug("滑动初始坐标:({},{}); 移动距离dX:{}, dy:{}".format(*origin, *movement))
        command = "input swipe {} {} {} {} ".format(x1, y1, x2, y2)
        if duration is not None:
            command += str(int(duration))
        self.run_device_cmd(command)

    def touch_tap(self, XY=None, offsets=None):
        # sleep(10)
        # sleep(0.5)
        if offsets is not None:
            final_X = XY[0] + randint(-offsets[0], offsets[0])
            final_Y = XY[1] + randint(-offsets[1], offsets[1])
        else:
            final_X = XY[0] + randint(-1, 1)
            final_Y = XY[1] + randint(-1, 1)
        # 如果你遇到了问题，可以把这百年输出并把日志分享到群里。
        logger.debug("点击坐标:({},{})".format(final_X, final_Y))
        command = "input tap {} {}".format(final_X,
                                           final_Y)
        self.run_device_cmd(command)

[((24, 11, 24, 50), 'PIL.Image.frombytes', 'Image.frombytes', ({(24, 27, 24, 33): '"""RGBA"""', (24, 35, 24, 41): '(w, h)', (24, 43, 24, 49): 'pixels'}, {}), "('RGBA', (w, h), pixels)", False, 'from PIL import Image\n'), ((30, 11, 30, 32), 'PIL.Image.open', 'Image.open', ({(30, 22, 30, 31): 'imgorfile'}, {}), '(imgorfile)', False, 'from PIL import Image\n'), ((50, 13, 50, 50), 'config.get', 'config.get', ({(50, 24, 50, 43): '"""device/adb_binary"""', (50, 45, 50, 49): 'None'}, {}), "('device/adb_binary', None)", False, 'import config\n'), ((77, 32, 77, 75), 'config.get', 'config.get', ({(77, 43, 77, 68): '"""device/cache_screenshot"""', (77, 70, 77, 74): 'True'}, {}), "('device/cache_screenshot', True)", False, 'import config\n'), ((82, 11, 82, 64), 'config.get', 'config.get', ({(82, 22, 82, 57): '"""device/try_emulator_enhanced_mode"""', (82, 59, 82, 63): '(True)'}, {}), "('device/try_emulator_enhanced_mode', True)", False, 'import config\n'), ((117, 28, 117, 76), 'config.get', 'config.get', ({(117, 39, 117, 69): '"""device/adb_always_use_device"""', (117, 71, 117, 75): 'None'}, {}), "('device/adb_always_use_device', None)", False, 'import config\n'), ((191, 15, 191, 34), 'util.socketutil.recvall', 'recvall', ({(191, 23, 191, 24): 's', (191, 26, 191, 33): '4194304'}, {}), '(s, 4194304)', False, 'from util.socketutil import recvall\n'), ((198, 15, 198, 34), 'util.socketutil.recvall', 'recvall', ({(198, 23, 198, 24): 's', (198, 26, 198, 33): '4194304'}, {}), '(s, 4194304)', False, 'from util.socketutil import recvall\n'), ((200, 15, 200, 66), 'zlib.decompress', 'zlib.decompress', ({(200, 31, 200, 35): 'data', (200, 37, 200, 56): 'zlib.MAX_WBITS | 16', (200, 58, 200, 65): '8388608'}, {}), '(data, zlib.MAX_WBITS | 16, 8388608)', False, 'import zlib\n'), ((201, 18, 201, 52), 'struct.unpack_from', 'struct.unpack_from', ({(201, 37, 201, 42): '"""III"""', (201, 44, 201, 48): 'data', (201, 50, 201, 51): '0'}, {}), "('III', data, 0)", False, 'import struct\n'), ((214, 18, 214, 52), 'struct.unpack_from', 'struct.unpack_from', ({(214, 37, 214, 42): '"""III"""', (214, 44, 214, 48): 'data', (214, 50, 214, 51): '0'}, {}), "('III', data, 0)", False, 'import struct\n'), ((219, 13, 219, 29), 'time.monotonic', 'time.monotonic', ({}, {}), '()', False, 'import time\n'), ((225, 13, 225, 29), 'time.monotonic', 'time.monotonic', ({}, {}), '()', False, 'import time\n'), ((52, 31, 52, 67), 'os.path.join', 'os.path.join', ({(52, 44, 52, 59): 'config.ADB_ROOT', (52, 61, 52, 66): '"""adb"""'}, {}), "(config.ADB_ROOT, 'adb')", False, 'import os\n'), ((58, 12, 58, 64), 'subprocess.run', 'subprocess.run', (), '', False, 'import subprocess\n'), ((104, 27, 104, 70), 'config.get', 'config.get', ({(104, 38, 104, 63): '"""device/adb_auto_connect"""', (104, 65, 104, 69): 'None'}, {}), "('device/adb_auto_connect', None)", False, 'import config\n'), ((245, 30, 245, 62), 'random.randint', 'randint', ({(245, 38, 245, 49): '(-offsets[0])', (245, 51, 245, 61): 'offsets[0]'}, {}), '(-offsets[0], offsets[0])', False, 'from random import randint\n'), ((246, 30, 246, 62), 'random.randint', 'randint', ({(246, 38, 246, 49): '(-offsets[1])', (246, 51, 246, 61): 'offsets[1]'}, {}), '(-offsets[1], offsets[1])', False, 'from random import randint\n'), ((248, 30, 248, 44), 'random.randint', 'randint', ({(248, 38, 248, 40): '(-1)', (248, 42, 248, 43): '(1)'}, {}), '(-1, 1)', False, 'from random import randint\n'), ((249, 30, 249, 44), 'random.randint', 'randint', ({(249, 38, 249, 40): '(-1)', (249, 42, 249, 43): '(1)'}, {}), '(-1, 1)', False, 'from random import randint\n'), ((213, 27, 213, 55), 'util.socketutil.recvall', 'recvall', ({(213, 35, 213, 39): 'conn', (213, 41, 213, 48): '8388608', (213, 50, 213, 54): 'True'}, {}), '(conn, 8388608, True)', False, 'from util.socketutil import recvall\n'), ((180, 31, 180, 44), 'util.socketutil.recvall', 'recvall', ({(180, 39, 180, 43): 'conn'}, {}), '(conn)', False, 'from util.socketutil import recvall\n')]