import ctypes.wintypes

CreateSymbolicLink = ctypes.windll.kernel32.CreateSymbolicLinkW
CreateSymbolicLink.argtypes = (
    ctypes.wintypes.LPWSTR,
    ctypes.wintypes.LPWSTR,
    ctypes.wintypes.DWORD,
)
CreateSymbolicLink.restype = ctypes.wintypes.BOOLEAN

CreateHardLink = ctypes.windll.kernel32.CreateHardLinkW
CreateHardLink.argtypes = (
    ctypes.wintypes.LPWSTR,
    ctypes.wintypes.LPWSTR,
    ctypes.wintypes.LPVOID,  # reserved for LPSECURITY_ATTRIBUTES
)
CreateHardLink.restype = ctypes.wintypes.BOOLEAN

GetFileAttributes = ctypes.windll.kernel32.GetFileAttributesW
GetFileAttributes.argtypes = (ctypes.wintypes.LPWSTR,)
GetFileAttributes.restype = ctypes.wintypes.DWORD

SetFileAttributes = ctypes.windll.kernel32.SetFileAttributesW
SetFileAttributes.argtypes = ctypes.wintypes.LPWSTR, ctypes.wintypes.DWORD
SetFileAttributes.restype = ctypes.wintypes.BOOL

MAX_PATH = 260

GetFinalPathNameByHandle = ctypes.windll.kernel32.GetFinalPathNameByHandleW
GetFinalPathNameByHandle.argtypes = (
    ctypes.wintypes.HANDLE,
    ctypes.wintypes.LPWSTR,
    ctypes.wintypes.DWORD,
    ctypes.wintypes.DWORD,
)
GetFinalPathNameByHandle.restype = ctypes.wintypes.DWORD


class SECURITY_ATTRIBUTES(ctypes.Structure):
    _fields_ = (
        ('length', ctypes.wintypes.DWORD),
        ('p_security_descriptor', ctypes.wintypes.LPVOID),
        ('inherit_handle', ctypes.wintypes.BOOLEAN),
    )


LPSECURITY_ATTRIBUTES = ctypes.POINTER(SECURITY_ATTRIBUTES)

CreateFile = ctypes.windll.kernel32.CreateFileW
CreateFile.argtypes = (
    ctypes.wintypes.LPWSTR,
    ctypes.wintypes.DWORD,
    ctypes.wintypes.DWORD,
    LPSECURITY_ATTRIBUTES,
    ctypes.wintypes.DWORD,
    ctypes.wintypes.DWORD,
    ctypes.wintypes.HANDLE,
)
CreateFile.restype = ctypes.wintypes.HANDLE
FILE_SHARE_READ = 1
FILE_SHARE_WRITE = 2
FILE_SHARE_DELETE = 4
FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
FILE_FLAG_BACKUP_SEMANTICS = 0x2000000
NULL = 0
OPEN_EXISTING = 3
FILE_ATTRIBUTE_READONLY = 0x1
FILE_ATTRIBUTE_HIDDEN = 0x2
FILE_ATTRIBUTE_DIRECTORY = 0x10
FILE_ATTRIBUTE_NORMAL = 0x80
FILE_ATTRIBUTE_REPARSE_POINT = 0x400
GENERIC_READ = 0x80000000
FILE_READ_ATTRIBUTES = 0x80
INVALID_HANDLE_VALUE = ctypes.wintypes.HANDLE(-1).value

INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF

ERROR_NO_MORE_FILES = 0x12

VOLUME_NAME_DOS = 0

CloseHandle = ctypes.windll.kernel32.CloseHandle
CloseHandle.argtypes = (ctypes.wintypes.HANDLE,)
CloseHandle.restype = ctypes.wintypes.BOOLEAN


class WIN32_FIND_DATA(ctypes.wintypes.WIN32_FIND_DATAW):
    """
    _fields_ = [
        ("dwFileAttributes", DWORD),
        ("ftCreationTime", FILETIME),
        ("ftLastAccessTime", FILETIME),
        ("ftLastWriteTime", FILETIME),
        ("nFileSizeHigh", DWORD),
        ("nFileSizeLow", DWORD),
        ("dwReserved0", DWORD),
        ("dwReserved1", DWORD),
        ("cFileName", WCHAR * MAX_PATH),
        ("cAlternateFileName", WCHAR * 14)]
    ]
    """

    @property
    def file_attributes(self):
        return self.dwFileAttributes

    @property
    def creation_time(self):
        return self.ftCreationTime

    @property
    def last_access_time(self):
        return self.ftLastAccessTime

    @property
    def last_write_time(self):
        return self.ftLastWriteTime

    @property
    def file_size_words(self):
        return [self.nFileSizeHigh, self.nFileSizeLow]

    @property
    def reserved(self):
        return [self.dwReserved0, self.dwReserved1]

    @property
    def filename(self):
        return self.cFileName

    @property
    def alternate_filename(self):
        return self.cAlternateFileName

    @property
    def file_size(self):
        return self.nFileSizeHigh << 32 + self.nFileSizeLow


LPWIN32_FIND_DATA = ctypes.POINTER(ctypes.wintypes.WIN32_FIND_DATAW)

FindFirstFile = ctypes.windll.kernel32.FindFirstFileW
FindFirstFile.argtypes = (ctypes.wintypes.LPWSTR, LPWIN32_FIND_DATA)
FindFirstFile.restype = ctypes.wintypes.HANDLE
FindNextFile = ctypes.windll.kernel32.FindNextFileW
FindNextFile.argtypes = (ctypes.wintypes.HANDLE, LPWIN32_FIND_DATA)
FindNextFile.restype = ctypes.wintypes.BOOLEAN

ctypes.windll.kernel32.FindClose.argtypes = (ctypes.wintypes.HANDLE,)

SCS_32BIT_BINARY = 0  # A 32-bit Windows-based application
SCS_64BIT_BINARY = 6  # A 64-bit Windows-based application
SCS_DOS_BINARY = 1  # An MS-DOS-based application
SCS_OS216_BINARY = 5  # A 16-bit OS/2-based application
SCS_PIF_BINARY = 3  # A PIF file that executes an MS-DOS-based application
SCS_POSIX_BINARY = 4  # A POSIX-based application
SCS_WOW_BINARY = 2  # A 16-bit Windows-based application

_GetBinaryType = ctypes.windll.kernel32.GetBinaryTypeW
_GetBinaryType.argtypes = (
    ctypes.wintypes.LPWSTR,
    ctypes.POINTER(ctypes.wintypes.DWORD),
)
_GetBinaryType.restype = ctypes.wintypes.BOOL

FILEOP_FLAGS = ctypes.wintypes.WORD


class BY_HANDLE_FILE_INFORMATION(ctypes.Structure):
    _fields_ = [
        ('file_attributes', ctypes.wintypes.DWORD),
        ('creation_time', ctypes.wintypes.FILETIME),
        ('last_access_time', ctypes.wintypes.FILETIME),
        ('last_write_time', ctypes.wintypes.FILETIME),
        ('volume_serial_number', ctypes.wintypes.DWORD),
        ('file_size_high', ctypes.wintypes.DWORD),
        ('file_size_low', ctypes.wintypes.DWORD),
        ('number_of_links', ctypes.wintypes.DWORD),
        ('file_index_high', ctypes.wintypes.DWORD),
        ('file_index_low', ctypes.wintypes.DWORD),
    ]

    @property
    def file_size(self):
        return (self.file_size_high << 32) + self.file_size_low

    @property
    def file_index(self):
        return (self.file_index_high << 32) + self.file_index_low


GetFileInformationByHandle = ctypes.windll.kernel32.GetFileInformationByHandle
GetFileInformationByHandle.restype = ctypes.wintypes.BOOL
GetFileInformationByHandle.argtypes = (
    ctypes.wintypes.HANDLE,
    ctypes.POINTER(BY_HANDLE_FILE_INFORMATION),
)


class SHFILEOPSTRUCT(ctypes.Structure):
    _fields_ = [
        ('status_dialog', ctypes.wintypes.HWND),
        ('operation', ctypes.wintypes.UINT),
        ('from_', ctypes.wintypes.LPWSTR),
        ('to', ctypes.wintypes.LPWSTR),
        ('flags', FILEOP_FLAGS),
        ('operations_aborted', ctypes.wintypes.BOOL),
        ('name_mapping_handles', ctypes.wintypes.LPVOID),
        ('progress_title', ctypes.wintypes.LPWSTR),
    ]


_SHFileOperation = ctypes.windll.shell32.SHFileOperationW
_SHFileOperation.argtypes = [ctypes.POINTER(SHFILEOPSTRUCT)]
_SHFileOperation.restype = ctypes.c_int

FOF_ALLOWUNDO = 64
FOF_NOCONFIRMATION = 16
FO_DELETE = 3

ReplaceFile = ctypes.windll.kernel32.ReplaceFileW
ReplaceFile.restype = ctypes.wintypes.BOOL
ReplaceFile.argtypes = [
    ctypes.wintypes.LPWSTR,
    ctypes.wintypes.LPWSTR,
    ctypes.wintypes.LPWSTR,
    ctypes.wintypes.DWORD,
    ctypes.wintypes.LPVOID,
    ctypes.wintypes.LPVOID,
]

REPLACEFILE_WRITE_THROUGH = 0x1
REPLACEFILE_IGNORE_MERGE_ERRORS = 0x2
REPLACEFILE_IGNORE_ACL_ERRORS = 0x4


class STAT_STRUCT(ctypes.Structure):
    _fields_ = [
        ('dev', ctypes.c_uint),
        ('ino', ctypes.c_ushort),
        ('mode', ctypes.c_ushort),
        ('nlink', ctypes.c_short),
        ('uid', ctypes.c_short),
        ('gid', ctypes.c_short),
        ('rdev', ctypes.c_uint),
        # the following 4 fields are ctypes.c_uint64 for _stat64
        ('size', ctypes.c_uint),
        ('atime', ctypes.c_uint),
        ('mtime', ctypes.c_uint),
        ('ctime', ctypes.c_uint),
    ]


_wstat = ctypes.windll.msvcrt._wstat
_wstat.argtypes = [ctypes.wintypes.LPWSTR, ctypes.POINTER(STAT_STRUCT)]
_wstat.restype = ctypes.c_int

FILE_NOTIFY_CHANGE_LAST_WRITE = 0x10

FindFirstChangeNotification = ctypes.windll.kernel32.FindFirstChangeNotificationW
FindFirstChangeNotification.argtypes = (
    ctypes.wintypes.LPWSTR,
    ctypes.wintypes.BOOL,
    ctypes.wintypes.DWORD,
)
FindFirstChangeNotification.restype = ctypes.wintypes.HANDLE

FindCloseChangeNotification = ctypes.windll.kernel32.FindCloseChangeNotification
FindCloseChangeNotification.argtypes = (ctypes.wintypes.HANDLE,)
FindCloseChangeNotification.restype = ctypes.wintypes.BOOL

FindNextChangeNotification = ctypes.windll.kernel32.FindNextChangeNotification
FindNextChangeNotification.argtypes = (ctypes.wintypes.HANDLE,)
FindNextChangeNotification.restype = ctypes.wintypes.BOOL

FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
IO_REPARSE_TAG_SYMLINK = 0xA000000C
FSCTL_GET_REPARSE_POINT = 0x900A8

LPDWORD = ctypes.POINTER(ctypes.wintypes.DWORD)
LPOVERLAPPED = ctypes.wintypes.LPVOID

DeviceIoControl = ctypes.windll.kernel32.DeviceIoControl
DeviceIoControl.argtypes = [
    ctypes.wintypes.HANDLE,
    ctypes.wintypes.DWORD,
    ctypes.wintypes.LPVOID,
    ctypes.wintypes.DWORD,
    ctypes.wintypes.LPVOID,
    ctypes.wintypes.DWORD,
    LPDWORD,
    LPOVERLAPPED,
]
DeviceIoControl.restype = ctypes.wintypes.BOOL


class REPARSE_DATA_BUFFER(ctypes.Structure):
    _fields_ = [
        ('tag', ctypes.c_ulong),
        ('data_length', ctypes.c_ushort),
        ('reserved', ctypes.c_ushort),
        ('substitute_name_offset', ctypes.c_ushort),
        ('substitute_name_length', ctypes.c_ushort),
        ('print_name_offset', ctypes.c_ushort),
        ('print_name_length', ctypes.c_ushort),
        ('flags', ctypes.c_ulong),
        ('path_buffer', ctypes.c_byte * 1),
    ]

    def get_print_name(self):
        wchar_size = ctypes.sizeof(ctypes.wintypes.WCHAR)
        arr_typ = ctypes.wintypes.WCHAR * (self.print_name_length // wchar_size)
        data = ctypes.byref(self.path_buffer, self.print_name_offset)
        return ctypes.cast(data, ctypes.POINTER(arr_typ)).contents.value

    def get_substitute_name(self):
        wchar_size = ctypes.sizeof(ctypes.wintypes.WCHAR)
        arr_typ = ctypes.wintypes.WCHAR * (self.substitute_name_length // wchar_size)
        data = ctypes.byref(self.path_buffer, self.substitute_name_offset)
        return ctypes.cast(data, ctypes.POINTER(arr_typ)).contents.value
