import win32security
import win32file
import win32event
import win32con
import win32api
import pywintypes


class WinBufferedReadFile(object):
    verbose = False

    def __init__(self, filename):
        self._hfile = win32file.CreateFile(filename,
                                           win32con.GENERIC_READ | win32con.GENERIC_WRITE,
                                           win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
                                           win32security.SECURITY_ATTRIBUTES(),
                                           win32con.OPEN_EXISTING,
                                           win32con.FILE_FLAG_OVERLAPPED,
                                           0)
        self._read_ovrlpd = pywintypes.OVERLAPPED()
        self._read_ovrlpd.hEvent = win32event.CreateEvent(None, True,
                                                          False, None)
        self._write_ovrlpd = pywintypes.OVERLAPPED()
        self._write_ovrlpd.hEvent = win32event.CreateEvent(None, True,
                                                           False, None)
        self._bufs = []
        self._n = 0

    def __del__(self):
        win32api.CloseHandle(self._read_ovrlpd.hEvent)
        win32api.CloseHandle(self._write_ovrlpd.hEvent)

    def close(self):
        win32api.CloseHandle(self._hfile)

    def write(self, s):
        win32file.WriteFile(self._hfile, s, self._write_ovrlpd)
        win32file.GetOverlappedResult(self._hfile, self._write_ovrlpd, True)

    def flush(self):
        pass  # TODO: flush in win32file?

    def read(self, n):
        while True:  # emulate blocking IO
            if self._n >= n:
                frags = []
                frags_length = 0
                if self.verbose:
                    txt = "get %s, | bufs = %s " % (n, self._n)
                    txt += "[%s]" % ','.join(map(lambda x: str(len(x)),
                                                 self._bufs))
                    print(txt)
                while frags_length < n:
                    frags.append(self._bufs.pop(0))
                    frags_length += len(frags[-1])
                self._n -= n
                whole = ''.join(frags)
                ret = whole[:n]
                rest = whole[n:]
                if len(rest) > 0:
                    self._bufs.append(rest)
                if self.verbose:
                    txt = "return %s(%s), | bufs = %s " % (len(ret), n,
                                                           self._n)
                    txt += "[%s]" % ','.join(map(lambda x: str(len(x)),
                                                 self._bufs))
                    print(txt)
                return ret
            try:
                # 4096 is the largest result viosdev will return right now.
                err, b = win32file.ReadFile(self._hfile, 4096,
                                            self._read_ovrlpd)
                nr = win32file.GetOverlappedResult(self._hfile,
                                                   self._read_ovrlpd,
                                                   True)
                if nr > 0:
                    self._bufs.append(b[:nr])
                    self._n += nr
                if self.verbose:
                    txt = "read %s, err %s | bufs = %s " % (nr, err, self._n)
                    txt += "[%s]" % ','.join(map(lambda x: str(len(x)),
                                                 self._bufs))
                    print(txt)
            except Exception:
                pass
        # Never Reached
        raise Exception("Error in WinBufferedReadFile - should never be reached")