# coding: utf-8
# codeskyblue 2020/06/03
#

__all__ = [
    'Color', 'AFC_MAGIC', 'AFCMode', 'AFC', 'AFCStatus', 'AFCPacket', 'LOCKDOWN_PORT', 'PROGRAM_NAME',
    'SYSMON_PROC_ATTRS', 'SYSMON_SYS_ATTRS', 'MODELS', 'LockdownService',
    "UsbmuxReplyCode", "InstrumentsService", "LOG", "StatResult", "UsbmuxMessageType", "ConnectionType"
]

from dataclasses import dataclass
import datetime
import enum
import typing


class LOG(str, enum.Enum):
    root = "tidevice"
    xcuitest = "tidevice.xctest"
    xcuitest_process_log = "tidevice.xctest.test_process_log"
    xcuitest_console_log = "tidevice.xctest.test_console_log"
    socket = "tidevice.socket"


class Color(enum.Enum):
    END = '\033[0m'
    BOLD = '\033[1m'

    # Foreground
    BLACK = '\33[30m'
    RED = '\33[31m'
    GREEN = '\33[32m'
    YELLOW = '\33[33m'
    BLUE = '\33[34m'
    VIOLET = '\33[35m'
    BEIGE = '\33[36m'
    WHITE = '\33[37m'

    # Background
    BG_GREEN = '\033[42m'

    @staticmethod
    def wrap_text(text, *colors):
        prefix = ''.join([c.value for c in colors])
        return prefix + text + Color.END.value


SYSMON_PROC_ATTRS = [
    "memVirtualSize",  # vss
    "cpuUsage",
    "ctxSwitch",  # the number of context switches by process each second
    "intWakeups",  # the number of threads wakeups by process each second
    "physFootprint",  # real memory (物理内存)
    "memResidentSize",  # rss
    "memAnon",  # anonymous memory
    "pid"
]

SYSMON_SYS_ATTRS = [
    "vmExtPageCount", "vmFreeCount", "vmPurgeableCount", "vmSpeculativeCount",
    "physMemSize"
]

LOCKDOWN_PORT = 62078
PROGRAM_NAME = "tidevice"

AFC_MAGIC = b"CFA6LPAA"


class AFCMode(enum.IntEnum):
    """ file open mode"""
    O_RDONLY = 0x00000001  #/**< r   O_RDONLY
    O_RW = 0x00000002  #/**< r+  O_RDWR   | O_CREAT
    O_WRONLY = 0x00000003  #/**< w   O_WRONLY | O_CREAT  | O_TRUNC
    O_WR = 0x00000004  #/**< w+  O_RDWR   | O_CREAT  | O_TRUNC
    O_APPEND = 0x00000005  #/**< a   O_WRONLY | O_APPEND | O_CREAT
    O_RDAPPEND = 0x00000006  #/**< a+  O_RDWR   | O_APPEND | O_CREAT


class AFC(enum.IntEnum):
    OP_INVALID = 0x00000000
    OP_STATUS = 0x00000001
    OP_DATA = 0x00000002  # Data
    OP_READ_DIR = 0x00000003  # ReadDir
    OP_READ_FILE = 0x00000004  # ReadFile
    OP_WRITE_FILE = 0x00000005  # WriteFile
    OP_WRITE_PART = 0x00000006  # WritePart
    OP_TRUNCATE = 0x00000007  # TruncateFile
    OP_REMOVE_PATH = 0x00000008  # RemovePath
    OP_MAKE_DIR = 0x00000009  # MakeDir
    OP_GET_FILE_INFO = 0x0000000a  # GetFileInfo
    OP_GET_DEVINFO = 0x0000000b  # GetDeviceInfo
    OP_WRITE_FILE_ATOM = 0x0000000c  # WriteFileAtomic (tmp file+rename)
    OP_FILE_OPEN = 0x0000000d  # FileRefOpen
    OP_FILE_OPEN_RES = 0x0000000e  # FileRefOpenResult
    OP_READ = 0x0000000f  # FileRefRead
    OP_WRITE = 0x00000010  # FileRefWrite
    OP_FILE_SEEK = 0x00000011  # FileRefSeek
    OP_FILE_TELL = 0x00000012  # FileRefTell
    OP_FILE_TELL_RES = 0x00000013  # FileRefTellResult
    OP_FILE_CLOSE = 0x00000014  # FileRefClose
    OP_FILE_SET_SIZE = 0x00000015  # FileRefSetFileSize (ftruncate)
    OP_GET_CON_INFO = 0x00000016  # GetConnectionInfo
    OP_SET_CON_OPTIONS = 0x00000017  # SetConnectionOptions
    OP_RENAME_PATH = 0x00000018  # RenamePath
    OP_SET_FS_BS = 0x00000019  # SetFSBlockSize (0x800000)
    OP_SET_SOCKET_BS = 0x0000001A  # SetSocketBlockSize (0x800000)
    OP_FILE_LOCK = 0x0000001B  # FileRefLock
    OP_MAKE_LINK = 0x0000001C  # MakeLink
    OP_SET_FILE_TIME = 0x0000001E  # set st_mtime
    OP_GET_FILE_HASH_RANGE = 0x0000001F  # GetFileHashWithRange

    HARDLINK = 1
    SYMLINK = 2

    LOCK_SH = 1 | 4  #/**< shared lock
    LOCK_EX = 2 | 4  #/**< exclusive lock
    LOCK_UN = 8 | 4  #/**< unlock


@enum.unique
class AFCStatus(enum.IntEnum):
    ST_SUCCESS = 0
    ST_UNKNOWN_ERROR = 1
    ST_OP_HEADER_INVALID = 2
    ST_NO_RESOURCES = 3
    ST_READ_ERROR = 4
    ST_WRITE_ERROR = 5
    ST_UNKNOWN_PACKET_TYPE = 6
    ST_INVALID_ARG = 7
    ST_OBJECT_NOT_FOUND = 8
    ST_OBJECT_IS_DIR = 9
    ST_PERM_DENIED = 10
    ST_SERVICE_NOT_CONNECTED = 11
    ST_OP_TIMEOUT = 12
    ST_TOO_MUCH_DATA = 13
    ST_END_OF_DATA = 14
    ST_OP_NOT_SUPPORTED = 15
    ST_OBJECT_EXISTS = 16
    ST_OBJECT_BUSY = 17
    ST_NO_SPACE_LEFT = 18
    ST_OP_WOULD_BLOCK = 19
    ST_IO_ERROR = 20
    ST_OP_INTERRUPTED = 21
    ST_OP_IN_PROGRESS = 22
    ST_INTERNAL_ERROR = 23

    ST_MUX_ERROR = 30
    ST_NO_MEM = 31
    ST_NOT_ENOUGH_DATA = 32
    ST_DIR_NOT_EMPTY = 33


class AFCPacket(typing.NamedTuple):
    status: AFCStatus
    data: bytes # contains fd when open file, other time always empty
    payload: bytes


# See also: https://www.theiphonewiki.com/wiki/Models
# TODO automation update models mapping use github action.
MODELS = {
    # iPhone
    "iPhone5,1": "iPhone 5",
    "iPhone5,2": "iPhone 5",
    "iPhone5,3": "iPhone 5c",
    "iPhone5,4": "iPhone 5c",
    "iPhone6,1": "iPhone 5s",
    "iPhone6,2": "iPhone 5s",
    "iPhone7,1": "iPhone 6 Plus",
    "iPhone7,2": "iPhone 6",
    "iPhone8,1": "iPhone 6s",
    "iPhone8,2": "iPhone 6s Plus",
    "iPhone8,4": "iPhone SE (1st generation)",
    "iPhone9,1": "iPhone 7",  # Global
    "iPhone9,2": "iPhone 7 Plus",  # Global
    "iPhone9,3": "iPhone 7",  # GSM
    "iPhone9,4": "iPhone 7 Plus",  # GSM
    "iPhone10,1": "iPhone 8",  # Global
    "iPhone10,2": "iPhone 8 Plus",  # Global
    "iPhone10,3": "iPhone X",  # Global
    "iPhone10,4": "iPhone 8",  # GSM
    "iPhone10,5": "iPhone 8 Plus",  # GSM
    "iPhone10,6": "iPhone X",  # GSM
    "iPhone11,2": "iPhone XS",
    "iPhone11,4": "iPhone XS Max",
    "iPhone11,6": "iPhone XS Max",
    "iPhone11,8": "iPhone XR",
    "iPhone12,1": "iPhone 11",
    "iPhone12,3": "iPhone 11 Pro",
    "iPhone12,5": "iPhone 11 Pro Max",
    "iPhone12,8": "iPhone SE (2nd generation)",
    "iPhone13,1": "iPhone 12 mini",
    "iPhone13,2": "iPhone 12",
    "iPhone13,3": "iPhone 12 Pro",
    "iPhone13,4": "iPhone 12 Pro Max",
    "iPhone14,2": "iPhone 13 Pro",
    "iPhone14,3": "iPhone 13 Pro Max",
    "iPhone14,4": "iPhone 13 mini",
    "iPhone14,5": "iPhone 13",
    "iPhone14,6": "iPhone SE (3rd generation)",
    "iPhone14,7": "iPhone 14",
    "iPhone14,8": "iPhone 14 Plus",
    "iPhone15,2": "iPhone 14 Pro",
    "iPhone15,3": "iPhone 14 Pro Max",

    # iPad mini
    "iPad2,5": "iPad mini",
    "iPad2,6": "iPad mini",
    "iPad2,7": "iPad mini",
    "iPad4,4": "iPad mini 2",
    "iPad4,5": "iPad mini 2",
    "iPad4,6": "iPad mini 2",
    "iPad4,7": "iPad mini 3",
    "iPad4,8": "iPad mini 3",
    "iPad4,9": "iPad mini 3",
    "iPad5,1": "iPad mini 4",
    "iPad5,2": "iPad mini 4",
    "iPad11,1": "iPad mini (5th generation)",
    "iPad11,2": "iPad mini (5th generation)",
    "iPad14,1": "iPad mini (6th generation)",
    "iPad14,2": "iPad mini (6th generation)",

    # iPad
    "iPad1,1": "iPad 1",
    "iPad2,1": "iPad (2rd generation)",
    "iPad2,2": "iPad (2rd generation)",
    "iPad2,3": "iPad (2rd generation)",
    "iPad2,4": "iPad (2rd generation)",
    "iPad3,1": "iPad (3rd generation)",
    "iPad3,2": "iPad (3rd generation)",
    "iPad3,3": "iPad (3rd generation)",
    "iPad3,4": "iPad (4rd generation)",
    "iPad3,5": "iPad (4rd generation)",
    "iPad3,6": "iPad (4rd generation)",
    "iPad6,11": "iPad (5th generation)",
    "iPad6,12": "iPad (5th generation)",
    "iPad7,5": "iPad (6th generation)",
    "iPad7,6": "iPad (6th generation)",
    "iPad7,11": "iPad (7th generation)",
    "iPad7,12": "iPad (7th generation)",
    "iPad11,6": "iPad (8th generation)",
    "iPad11,7": "iPad (8th generation)",
    "iPad12,1": "iPad (9th generation)",
    "iPad12,2": "iPad (9th generation)",

    # iPad Air
    "iPad4,1": "iPad Air",
    "iPad4,2": "iPad Air",
    "iPad4,3": "iPad Air",
    "iPad5,3": "iPad Air 2",
    "iPad5,4": "iPad Air 2",
    "iPad13,1": "iPad Air (4th generation)",
    "iPad13,2": "iPad Air (4th generation)",
    "iPad11,3": "iPad Air (3th generation)",
    "iPad11,4": "iPad Air (3th generation)",
    "iPad13,1": "iPad Air (4th generation)",
    "iPad13,2": "iPad Air (4th generation)",

    # iPad Pro
    "iPad6,3": "iPad Pro (9.7-inch)",
    "iPad6,4": "iPad Pro (9.7-inch)",
    "iPad6,7": "iPad Pro (12.9-inch)",
    "iPad6,8": "iPad Pro (12.9-inch)",
    "iPad7,1": "iPad Pro (12.9-inch) (2nd generation)",
    "iPad7,2": "iPad Pro (12.9-inch) (2nd generation)",
    "iPad7,3": "iPad Pro (10.5-inch)",
    "iPad7,4": "iPad Pro (10.5-inch)",
    "iPad8,1": "iPad Pro (11-inch)",
    "iPad8,2": "iPad Pro (11-inch)",
    "iPad8,3": "iPad Pro (11-inch)",
    "iPad8,4": "iPad Pro (11-inch)",
    "iPad8,5": "iPad Pro (12.9-inch) (3rd generation)",
    "iPad8,6": "iPad Pro (12.9-inch) (3rd generation)",
    "iPad8,7": "iPad Pro (12.9-inch) (3rd generation)",
    "iPad8,8": "iPad Pro (12.9-inch) (3rd generation)",
    "iPad8,9": "iPad Pro (11-inch) (2nd generation)",
    "iPad8,10": "iPad Pro (11-inch) (2nd generation)",
    "iPad8,11": "iPad Pro (12.9-inch) (4th generation)",
    "iPad8,12": "iPad Pro (12.9-inch) (4th generation)",
    "iPad13,4": "iPad Pro (11-inch) (3rd generation)",
    "iPad13,5": "iPad Pro (11-inch) (3rd generation)",
    "iPad13,6": "iPad Pro (11-inch) (3rd generation)",
    "iPad13,7": "iPad Pro (11-inch) (3rd generation)",
    "iPad13,8": "iPad Pro (12.9-inch) (5th generation)",
    "iPad13,9": "iPad Pro (12.9-inch) (5th generation)",
    "iPad13,10": "iPad Pro (12.9-inch) (5th generation)",
    "iPad13,11": "iPad Pro (12.9-inch) (5th generation)",

    # simulator
    "i386": "iPhone Simulator",
    "x86_64": "iPhone Simulator",

    # TODO iPod touch, etc ...
}


class LockdownService(str, enum.Enum):
    # hdiutil mount /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/14.0/DeveloperDiskImage.dmg
    # tree /Volumes/DeveloperDiskImage/Library/Lockdown
    MobileLockdown = 'com.apple.mobile.lockdown'

    CRASH_REPORT_MOVER_SERVICE = "com.apple.crashreportmover"
    CRASH_REPORT_COPY_MOBILE_SERVICE = "com.apple.crashreportcopymobile"

    # Ref: https://github.com/anonymous5l/iConsole/blob/master/wifiSync.go
    MobileWirelessLockdown = "com.apple.mobile.wireless_lockdown"
    InstallationProxy = "com.apple.mobile.installation_proxy"

    MobileScreenshotr = "com.apple.mobile.screenshotr"  # 截图服务
    MobileHouseArrest = "com.apple.mobile.house_arrest"  # 访问文件内的沙箱
    AFC = "com.apple.afc"  # 访问系统资源

    InstrumentsRemoteServer = "com.apple.instruments.remoteserver"
    InstrumentsRemoteServerSecure = "com.apple.instruments.remoteserver.DVTSecureSocketProxy"  # for iOS 14.0
    TestmanagerdLockdown = "com.apple.testmanagerd.lockdown"
    TestmanagerdLockdownSecure = "com.apple.testmanagerd.lockdown.secure"  # for iOS 14.0
    AmfiLockdown = "com.apple.amfi.lockdown" # iOS >= 15.7


class InstrumentsService(str, enum.Enum):
    DeviceInfo = 'com.apple.instruments.server.services.deviceinfo'
    ProcessControl = "com.apple.instruments.server.services.processcontrol"
    DeviceApplictionListing = "com.apple.instruments.server.services.device.applictionListing"
    GraphicsOpengl = "com.apple.instruments.server.services.graphics.opengl"  # 获取FPS
    Sysmontap = "com.apple.instruments.server.services.sysmontap"  # 获取性能数据用
    XcodeNetworkStatistics = 'com.apple.xcode.debug-gauge-data-providers.NetworkStatistics'  # 获取单进程网络数据
    XcodeEnergyStatistics = 'com.apple.xcode.debug-gauge-data-providers.Energy' # 获取功耗数据
    Networking = 'com.apple.instruments.server.services.networking'  # 全局网络数据 instruments 用的就是这个
    MobileNotifications = 'com.apple.instruments.server.services.mobilenotifications'  # 监控应用状态


class UsbmuxReplyCode(int, enum.Enum):
    """
    Ref: https://github.com/libimobiledevice/usbmuxd/blob/master/src/usbmuxd-proto.h
    """
    OK = 0
    BadCommand = 1
    BadDevice = 2
    ConnectionRefused = 3
    BadVersion = 6


@dataclass
class StatResult:
    st_name: str
    st_ifmt: str
    st_size: int
    st_blocks: int
    st_nlink: int
    st_ctime: datetime.datetime
    st_mtime: datetime.datetime
    st_linktarget: str = None

    def is_dir(self) -> bool:
        return self.st_ifmt == "S_IFDIR"

    def is_link(self) -> bool:
        return self.st_ifmt == "S_IFLNK"



class UsbmuxMessageType(enum.IntEnum):
    RESULT = 1
    CONNECT = 2
    LISTEN = 3
    ADD = 4
    REMOVE = 5
    PAIRED = 6
    PLIST = 8


class ConnectionType(str, enum.Enum):
    USB = "usb"
    NETWORK = "network"
