import t, c
from stdint import *
import stdio
import w32.win32base
import w32.win32file


class MODE(t.CEnum):
    R = 0
    W = 1
    A = 2
    RP = 3
    WP = 4
    AP = 5
    X = 6
    XP = 7


class FRESULT(t.CEnum):
    OK = 0
    ERR = -1
    ERR_CLOSED = -2
    ERR_PERM = -3
    ERR_IO = -4
    ERR_NOTFOUND = -5


SEEK_SET: t.CDefine = 0
SEEK_CUR: t.CDefine = 1
SEEK_END: t.CDefine = 2

INVALID_SET_FILE_POINTER: t.CDefine = -1

SHARE_READ: t.CDefine = 0x00000001
SHARE_WRITE: t.CDefine = 0x00000002
SHARE_DELETE: t.CDefine = 0x00000004


class File:
    handle: w32.win32base.HANDLE
    closed: bool
    can_read: bool
    can_write: bool
    is_append: bool
    _share_mode: ULONG

    def __init__(self, filename: str, mode: ULONG, share: ULONG = SHARE_READ):
        self.closed = True
        self.can_read = False
        self.can_write = False
        self.is_append = False
        self._share_mode = share

        access: ULONG = 0
        disposition: ULONG = w32.win32file.OPEN_EXISTING

        if mode == MODE.R:
            access = w32.win32file.GENERIC_READ
            disposition = w32.win32file.OPEN_EXISTING
            self.can_read = True
        elif mode == MODE.W:
            access = w32.win32file.GENERIC_WRITE
            disposition = w32.win32file.CREATE_ALWAYS
            self.can_write = True
        elif mode == MODE.A:
            access = w32.win32file.GENERIC_WRITE
            disposition = w32.win32file.OPEN_ALWAYS
            self.can_write = True
            self.is_append = True
        elif mode == MODE.RP:
            access = w32.win32file.GENERIC_READ | w32.win32file.GENERIC_WRITE
            disposition = w32.win32file.OPEN_EXISTING
            self.can_read = True
            self.can_write = True
        elif mode == MODE.WP:
            access = w32.win32file.GENERIC_READ | w32.win32file.GENERIC_WRITE
            disposition = w32.win32file.CREATE_ALWAYS
            self.can_read = True
            self.can_write = True
        elif mode == MODE.AP:
            access = w32.win32file.GENERIC_READ | w32.win32file.GENERIC_WRITE
            disposition = w32.win32file.OPEN_ALWAYS
            self.can_read = True
            self.can_write = True
            self.is_append = True
        elif mode == MODE.X:
            access = w32.win32file.GENERIC_WRITE
            disposition = w32.win32file.CREATE_NEW
            self.can_write = True
        elif mode == MODE.XP:
            access = w32.win32file.GENERIC_READ | w32.win32file.GENERIC_WRITE
            disposition = w32.win32file.CREATE_NEW
            self.can_read = True
            self.can_write = True
        else:
            access = w32.win32file.GENERIC_READ
            disposition = w32.win32file.OPEN_EXISTING
            self.can_read = True

        self.handle = w32.win32file.CreateFileA(
            filename, access, self._share_mode,
            None, disposition, w32.win32file.FILE_ATTRIBUTE_NORMAL, None
        )

        if self.handle == w32.win32base.INVALID_HANDLE_VALUE:
            self.closed = True
            return

        self.closed = False

        if self.is_append:
            self.seek(0, SEEK_END)

    def __enter__(self) -> 'File' | t.CPtr:
        return self

    def __exit__(self):
        self.close()

    def read(self, buf: bytes, count: ULONG) -> LONG:
        if self.closed: return FRESULT.ERR_CLOSED
        if not self.can_read: return FRESULT.ERR_PERM
        bytes_read: ULONG = 0
        result: BOOL = w32.win32file.ReadFile(self.handle, buf, count, t.CPtr(c.Addr(bytes_read)), None)
        if result == 0: return FRESULT.ERR_IO
        return LONG(bytes_read)

    def write(self, buf: bytes, count: ULONG) -> LONG:
        if self.closed: return FRESULT.ERR_CLOSED
        if not self.can_write: return FRESULT.ERR_PERM
        bytes_written: ULONG = 0
        result: BOOL = w32.win32file.WriteFile(self.handle, buf, count, t.CPtr(c.Addr(bytes_written)), None)
        if result == 0: return FRESULT.ERR_IO
        return LONG(bytes_written)

    def write_str(self, s: str) -> LONG:
        if self.closed: return FRESULT.ERR_CLOSED
        if not self.can_write: return FRESULT.ERR_PERM
        length: ULONG = 0
        p: bytes = s
        while c.Deref(p) != 0:
            length += 1
            p += 1
        return self.write(s, length)

    def seek(self, offset: LONG, origin: ULONG) -> LONG:
        if self.closed: return FRESULT.ERR_CLOSED
        result: LONG = w32.win32file.SetFilePointer(self.handle, offset, None, origin)
        if result == -1: return FRESULT.ERR_IO
        return result

    def tell(self) -> LONG:
        if self.closed: return FRESULT.ERR_CLOSED
        return self.seek(0, SEEK_CUR)

    def close(self) -> LONG:
        if self.closed: return FRESULT.ERR_CLOSED
        w32.win32base.CloseHandle(self.handle)
        self.closed = True
        return FRESULT.OK

    def flush(self) -> LONG:
        if self.closed: return FRESULT.ERR_CLOSED
        w32.win32file.FlushFileBuffers(self.handle)
        return FRESULT.OK

    def size(self) -> LONGLONG:
        if self.closed: return -1
        li: w32.win32base.LARGE_INTEGER = w32.win32base.LARGE_INTEGER()
        result: BOOL = w32.win32file.GetFileSizeEx(self.handle, t.CPtr(c.Addr(li)))
        if result == 0: return -1
        return li.QuadPart

    def readline(self, buf: bytes, max_count: ULONG) -> LONG:
        if self.closed: return FRESULT.ERR_CLOSED
        if not self.can_read: return FRESULT.ERR_PERM
        if max_count < 2: return FRESULT.ERR
        total: ULONG = 0
        bytes_read: ULONG = 0
        ch: t.CInt = 0
        while total < max_count - 1:
            bytes_read = 0
            result: BOOL = w32.win32file.ReadFile(self.handle, buf + total, 1, t.CPtr(c.Addr(bytes_read)), None)
            if result == 0 or bytes_read == 0: break
            ch = c.Deref(buf + total)
            total += 1
            if ch == 10: break
        if total < max_count:
            buf[total] = 0
        return LONG(total)

    def read_all(self, buf: bytes, max_count: ULONG) -> LONG:
        if self.closed: return FRESULT.ERR_CLOSED
        if not self.can_read: return FRESULT.ERR_PERM
        if max_count < 2: return FRESULT.ERR
        # stdio.printf("[DBG read_all] before size()\n")
        file_size: LONGLONG = self.size()
        # stdio.printf("[DBG read_all] after size(), file_size=%lld\n", file_size)
        if file_size < 0: return FRESULT.ERR_IO
        to_read: ULONG = 0
        if file_size < max_count - 1:
            to_read = ULONG(file_size)
        else:
            to_read = max_count - 1
        # stdio.printf("[DBG read_all] to_read=%u, before seek\n", to_read)
        self.seek(0, SEEK_SET)
        # stdio.printf("[DBG read_all] after seek, before ReadFile\n")
        bytes_read: ULONG = 0
        result: BOOL = w32.win32file.ReadFile(self.handle, buf, to_read, t.CPtr(c.Addr(bytes_read)), None)
        # stdio.printf("[DBG read_all] after ReadFile, result=%d, bytes_read=%u\n", result, bytes_read)
        if result == 0: return FRESULT.ERR_IO
        if bytes_read < max_count:
            buf[bytes_read] = 0
        return LONG(bytes_read)


class FileW:
    handle: w32.win32base.HANDLE
    closed: bool
    can_read: bool
    can_write: bool
    is_append: bool
    _share_mode: ULONG

    def __init__(self, filename: w32.win32base.LPCWSTR, mode: ULONG, share: ULONG = SHARE_READ):
        self.closed = True
        self.can_read = False
        self.can_write = False
        self.is_append = False
        self._share_mode = share

        access: ULONG = 0
        disposition: ULONG = w32.win32file.OPEN_EXISTING

        if mode == MODE.R:
            access = w32.win32file.GENERIC_READ
            disposition = w32.win32file.OPEN_EXISTING
            self.can_read = True
        elif mode == MODE.W:
            access = w32.win32file.GENERIC_WRITE
            disposition = w32.win32file.CREATE_ALWAYS
            self.can_write = True
        elif mode == MODE.A:
            access = w32.win32file.GENERIC_WRITE
            disposition = w32.win32file.OPEN_ALWAYS
            self.can_write = True
            self.is_append = True
        elif mode == MODE.RP:
            access = w32.win32file.GENERIC_READ | w32.win32file.GENERIC_WRITE
            disposition = w32.win32file.OPEN_EXISTING
            self.can_read = True
            self.can_write = True
        elif mode == MODE.WP:
            access = w32.win32file.GENERIC_READ | w32.win32file.GENERIC_WRITE
            disposition = w32.win32file.CREATE_ALWAYS
            self.can_read = True
            self.can_write = True
        elif mode == MODE.AP:
            access = w32.win32file.GENERIC_READ | w32.win32file.GENERIC_WRITE
            disposition = w32.win32file.OPEN_ALWAYS
            self.can_read = True
            self.can_write = True
            self.is_append = True
        elif mode == MODE.X:
            access = w32.win32file.GENERIC_WRITE
            disposition = w32.win32file.CREATE_NEW
            self.can_write = True
        elif mode == MODE.XP:
            access = w32.win32file.GENERIC_READ | w32.win32file.GENERIC_WRITE
            disposition = w32.win32file.CREATE_NEW
            self.can_read = True
            self.can_write = True
        else:
            access = w32.win32file.GENERIC_READ
            disposition = w32.win32file.OPEN_EXISTING
            self.can_read = True

        self.handle = w32.win32file.CreateFileW(
            filename, access, self._share_mode,
            None, disposition, w32.win32file.FILE_ATTRIBUTE_NORMAL, None
        )

        if self.handle == w32.win32base.INVALID_HANDLE_VALUE:
            self.closed = True
            return

        self.closed = False

        if self.is_append:
            self.seek(0, SEEK_END)

    def __enter__(self) -> 'FileW' | t.CPtr:
        return self

    def __exit__(self):
        self.close()

    def read(self, buf: bytes, count: ULONG) -> LONG:
        if self.closed: return FRESULT.ERR_CLOSED
        if not self.can_read: return FRESULT.ERR_PERM
        bytes_read: ULONG = 0
        result: BOOL = w32.win32file.ReadFile(self.handle, buf, count, t.CPtr(c.Addr(bytes_read)), None)
        if result == 0: return FRESULT.ERR_IO
        return LONG(bytes_read)

    def write(self, buf: bytes, count: ULONG) -> LONG:
        if self.closed: return FRESULT.ERR_CLOSED
        if not self.can_write: return FRESULT.ERR_PERM
        bytes_written: ULONG = 0
        result: BOOL = w32.win32file.WriteFile(self.handle, buf, count, t.CPtr(c.Addr(bytes_written)), None)
        if result == 0: return FRESULT.ERR_IO
        return LONG(bytes_written)

    def write_str(self, s: str) -> LONG:
        if self.closed: return FRESULT.ERR_CLOSED
        if not self.can_write: return FRESULT.ERR_PERM
        length: ULONG = 0
        p: bytes = s
        while c.Deref(p) != 0:
            length += 1
            p += 1
        return self.write(s, length)

    def seek(self, offset: LONG, origin: ULONG) -> LONG:
        if self.closed: return FRESULT.ERR_CLOSED
        result: LONG = w32.win32file.SetFilePointer(self.handle, offset, None, origin)
        if result == -1: return FRESULT.ERR_IO
        return result

    def tell(self) -> LONG:
        if self.closed: return FRESULT.ERR_CLOSED
        return self.seek(0, SEEK_CUR)

    def close(self) -> LONG:
        if self.closed: return FRESULT.ERR_CLOSED
        w32.win32base.CloseHandle(self.handle)
        self.closed = True
        return FRESULT.OK

    def flush(self) -> LONG:
        if self.closed: return FRESULT.ERR_CLOSED
        w32.win32file.FlushFileBuffers(self.handle)
        return FRESULT.OK

    def size(self) -> LONGLONG:
        if self.closed: return -1
        li: w32.win32base.LARGE_INTEGER = w32.win32base.LARGE_INTEGER()
        result: BOOL = w32.win32file.GetFileSizeEx(self.handle, t.CPtr(c.Addr(li)))
        if result == 0: return -1
        return li.QuadPart

    def readline(self, buf: bytes, max_count: ULONG) -> LONG:
        if self.closed: return FRESULT.ERR_CLOSED
        if not self.can_read: return FRESULT.ERR_PERM
        if max_count < 2: return FRESULT.ERR
        total: ULONG = 0
        bytes_read: ULONG = 0
        ch: t.CInt = 0
        while total < max_count - 1:
            bytes_read = 0
            result: BOOL = w32.win32file.ReadFile(self.handle, buf + total, 1, t.CPtr(c.Addr(bytes_read)), None)
            if result == 0 or bytes_read == 0: break
            ch = c.Deref(buf + total)
            total += 1
            if ch == 10: break
        if total < max_count:
            buf[total] = 0
        return LONG(total)

    def read_all(self, buf: bytes, max_count: ULONG) -> LONG:
        if self.closed: return FRESULT.ERR_CLOSED
        if not self.can_read: return FRESULT.ERR_PERM
        if max_count < 2: return FRESULT.ERR
        file_size: LONGLONG = self.size()
        if file_size < 0: return FRESULT.ERR_IO
        to_read: ULONG = 0
        if file_size < max_count - 1:
            to_read = ULONG(file_size)
        else:
            to_read = max_count - 1
        self.seek(0, SEEK_SET)
        bytes_read: ULONG = 0
        result: BOOL = w32.win32file.ReadFile(self.handle, buf, to_read, t.CPtr(c.Addr(bytes_read)), None)
        if result == 0: return FRESULT.ERR_IO
        if bytes_read < max_count:
            buf[bytes_read] = 0
        return LONG(bytes_read)


def open(filename: str, mode: ULONG, share: ULONG = SHARE_READ) -> File | t.CPtr:
    return File(filename, mode, share)

def openw(filename: LPCWSTR, mode: ULONG, share: ULONG = SHARE_READ) -> FileW | t.CPtr:
    return FileW(filename, mode, share)
