# No cancel button.

from pywin.mfc import dialog
from pywin.mfc.thread import WinThread
import threading
import win32ui
import win32con
import win32api
import time


def MakeProgressDlgTemplate(caption, staticText=""):
    style = (
        win32con.DS_MODALFRAME
        | win32con.WS_POPUP
        | win32con.WS_VISIBLE
        | win32con.WS_CAPTION
        | win32con.WS_SYSMENU
        | win32con.DS_SETFONT
    )
    cs = win32con.WS_CHILD | win32con.WS_VISIBLE

    w = 215
    h = 36  # With button
    h = 40

    dlg = [
        [caption, (0, 0, w, h), style, None, (8, "MS Sans Serif")],
    ]

    s = win32con.WS_TABSTOP | cs

    dlg.append([130, staticText, 1000, (7, 7, w - 7, h - 32), cs | win32con.SS_LEFT])

    #    dlg.append([128,
    # 		"Cancel",
    # 		win32con.IDCANCEL,
    # 		(w - 60, h - 18, 50, 14), s | win32con.BS_PUSHBUTTON])

    return dlg


class CStatusProgressDialog(dialog.Dialog):
    def __init__(self, title, msg="", maxticks=100, tickincr=1):
        self.initMsg = msg
        templ = MakeProgressDlgTemplate(title, msg)
        dialog.Dialog.__init__(self, templ)
        self.maxticks = maxticks
        self.tickincr = tickincr
        self.pbar = None

    def OnInitDialog(self):
        rc = dialog.Dialog.OnInitDialog(self)
        self.static = self.GetDlgItem(1000)
        self.pbar = win32ui.CreateProgressCtrl()
        self.pbar.CreateWindow(
            win32con.WS_CHILD | win32con.WS_VISIBLE, (10, 30, 310, 44), self, 1001
        )
        self.pbar.SetRange(0, self.maxticks)
        self.pbar.SetStep(self.tickincr)
        self.progress = 0
        self.pincr = 5
        return rc

    def Close(self):
        self.EndDialog(0)

    def SetMaxTicks(self, maxticks):
        if self.pbar is not None:
            self.pbar.SetRange(0, maxticks)

    def Tick(self):
        if self.pbar is not None:
            self.pbar.StepIt()

    def SetTitle(self, text):
        self.SetWindowText(text)

    def SetText(self, text):
        self.SetDlgItemText(1000, text)

    def Set(self, pos, max=None):
        if self.pbar is not None:
            self.pbar.SetPos(pos)
            if max is not None:
                self.pbar.SetRange(0, max)


# a progress dialog created in a new thread - especially suitable for
# console apps with no message loop.
MYWM_SETTITLE = win32con.WM_USER + 10
MYWM_SETMSG = win32con.WM_USER + 11
MYWM_TICK = win32con.WM_USER + 12
MYWM_SETMAXTICKS = win32con.WM_USER + 13
MYWM_SET = win32con.WM_USER + 14


class CThreadedStatusProcessDialog(CStatusProgressDialog):
    def __init__(self, title, msg="", maxticks=100, tickincr=1):
        self.title = title
        self.msg = msg
        self.threadid = win32api.GetCurrentThreadId()
        CStatusProgressDialog.__init__(self, title, msg, maxticks, tickincr)

    def OnInitDialog(self):
        rc = CStatusProgressDialog.OnInitDialog(self)
        self.HookMessage(self.OnTitle, MYWM_SETTITLE)
        self.HookMessage(self.OnMsg, MYWM_SETMSG)
        self.HookMessage(self.OnTick, MYWM_TICK)
        self.HookMessage(self.OnMaxTicks, MYWM_SETMAXTICKS)
        self.HookMessage(self.OnSet, MYWM_SET)
        return rc

    def _Send(self, msg):
        try:
            self.PostMessage(msg)
        except win32ui.error:
            # the user closed the window - but this does not cancel the
            # process - so just ignore it.
            pass

    def OnTitle(self, msg):
        CStatusProgressDialog.SetTitle(self, self.title)

    def OnMsg(self, msg):
        CStatusProgressDialog.SetText(self, self.msg)

    def OnTick(self, msg):
        CStatusProgressDialog.Tick(self)

    def OnMaxTicks(self, msg):
        CStatusProgressDialog.SetMaxTicks(self, self.maxticks)

    def OnSet(self, msg):
        CStatusProgressDialog.Set(self, self.pos, self.max)

    def Close(self):
        assert self.threadid, "No thread!"
        win32api.PostThreadMessage(self.threadid, win32con.WM_QUIT, 0, 0)

    def SetMaxTicks(self, maxticks):
        self.maxticks = maxticks
        self._Send(MYWM_SETMAXTICKS)

    def SetTitle(self, title):
        self.title = title
        self._Send(MYWM_SETTITLE)

    def SetText(self, text):
        self.msg = text
        self._Send(MYWM_SETMSG)

    def Tick(self):
        self._Send(MYWM_TICK)

    def Set(self, pos, max=None):
        self.pos = pos
        self.max = max
        self._Send(MYWM_SET)


class ProgressThread(WinThread):
    def __init__(self, title, msg="", maxticks=100, tickincr=1):
        self.title = title
        self.msg = msg
        self.maxticks = maxticks
        self.tickincr = tickincr
        self.dialog = None
        WinThread.__init__(self)
        self.createdEvent = threading.Event()

    def InitInstance(self):
        self.dialog = CThreadedStatusProcessDialog(
            self.title, self.msg, self.maxticks, self.tickincr
        )
        self.dialog.CreateWindow()
        try:
            self.dialog.SetForegroundWindow()
        except win32ui.error:
            pass
        self.createdEvent.set()
        return WinThread.InitInstance(self)

    def ExitInstance(self):
        return 0


def StatusProgressDialog(title, msg="", maxticks=100, parent=None):
    d = CStatusProgressDialog(title, msg, maxticks)
    d.CreateWindow(parent)
    return d


def ThreadedStatusProgressDialog(title, msg="", maxticks=100):
    t = ProgressThread(title, msg, maxticks)
    t.CreateThread()
    # Need to run a basic "PumpWaitingMessages" loop just incase we are
    # running inside Pythonwin.
    # Basic timeout incase things go terribly wrong.  Ideally we should use
    # win32event.MsgWaitForMultipleObjects(), but we use a threading module
    # event - so use a dumb strategy
    end_time = time.time() + 10
    while time.time() < end_time:
        if t.createdEvent.isSet():
            break
        win32ui.PumpWaitingMessages()
        time.sleep(0.1)
    return t.dialog


def demo():
    d = StatusProgressDialog("A Demo", "Doing something...")
    import win32api

    for i in range(100):
        if i == 50:
            d.SetText("Getting there...")
        if i == 90:
            d.SetText("Nearly done...")
        win32api.Sleep(20)
        d.Tick()
    d.Close()


def thread_demo():
    d = ThreadedStatusProgressDialog("A threaded demo", "Doing something")
    import win32api

    for i in range(100):
        if i == 50:
            d.SetText("Getting there...")
        if i == 90:
            d.SetText("Nearly done...")
        win32api.Sleep(20)
        d.Tick()
    d.Close()


if __name__ == "__main__":
    thread_demo()
    # demo()
