# Control Processors for our wizard

# This module is part of the spambayes project, which is Copyright 2003
# The Python Software Foundation and is covered by the Python Software
# Foundation license.

import win32gui, win32con, win32api, commctrl
from dialogs import ShowDialog, MakePropertyPage

try:
    enumerate
except NameError:   # enumerate new in 2.3
    def enumerate(seq):
        return [(i, seq[i]) for i in xrange(len(seq))]

import processors
import opt_processors
import async_processor

import timer

# An "abstract" wizard class.  Not technically abstract - this version
# supports sequential stepping through all the pages.  It is expected
# sub-classes will override "getNextPage" and "atFinish" to provide a
# custom navigation path.
class WizardButtonProcessor(processors.ButtonProcessor):
    def __init__(self, window, control_ids, pages, finish_fn):
        processors.ButtonProcessor.__init__(self, window,control_ids)
        self.back_btn_id = self.other_ids[0]
        self.page_ids = pages.split()
        self.currentPage = None
        self.currentPageIndex = -1
        self.currentPageHwnd = None
        self.finish_fn = finish_fn
        self.page_placeholder_id = self.other_ids[1]

    def Init(self):
        processors.ButtonProcessor.Init(self)
        self.back_btn_hwnd = self.GetControl(self.back_btn_id)
        self.forward_btn_hwnd = self.GetControl()
        self.forward_captions = win32gui.GetWindowText(self.forward_btn_hwnd).split(",")
        self.page_placeholder_hwnd = self.GetControl(self.page_placeholder_id)
        self.page_stack = []
        self.switchToPage(0)
        # brute-force timer to check if we can move forward.
        self.timer_id = timer.set_timer(800, self.OnCheckForwardTimer)

    def Done(self):
        if self.timer_id is not None:
            timer.kill_timer(self.timer_id)
            self.timer_id = None
        return processors.ButtonProcessor.Done(self)

    def changeControls(self):
        win32gui.EnableWindow(self.back_btn_hwnd,self.currentPageIndex!=0)
        if self.canGoNext():
            enabled = 1
        else:
            enabled = 0
        win32gui.EnableWindow(self.forward_btn_hwnd,enabled)
        index = 0
        if self.atFinish():
            index = 1
        win32gui.SetWindowText(self.forward_btn_hwnd, self.forward_captions[index])

    # No obvious way to communicate the state of what the "Forward" button
    # should be.  brute-force - check a config boolean on a timer.
    def OnCheckForwardTimer(self, event, time):
        #print "Timer fired"
        if self.canGoNext():
            enabled = 1
        else:
            enabled = 0
        win32gui.EnableWindow(self.forward_btn_hwnd,enabled)

    def OnClicked(self, id):
        if id == self.control_id:
            if self.atFinish():
                if not self.currentPage.SaveAllControls():
                    return
                #finish
                win32gui.EnableWindow(self.forward_btn_hwnd, False)
                win32gui.EnableWindow(self.back_btn_hwnd, False)
                try:
                    #optional
                    h = GetControl(self.window.manager.dialog_parser.ids["IDCANCEL"])
                    win32gui.EnableWindow(h, False)
                except:
                    pass

                self.finish_fn(self.window.manager, self.window)
                win32gui.EndDialog(self.window.hwnd, win32con.IDOK)
            else:
                #forward
                if self.canGoNext() and self.currentPage.SaveAllControls():
                    self.page_stack.append(self.currentPageIndex)
                    nextPage = self.getNextPageIndex()
                    self.switchToPage(nextPage)
        elif id == self.back_btn_id:
            #backward
            assert self.page_stack, "Back should be disabled when no back stack"
            pageNo = self.page_stack.pop()
            print "Back button switching to page", pageNo
            self.switchToPage(pageNo)

    def switchToPage(self, index):
        if self.currentPageHwnd is not None:
            if not self.currentPage.SaveAllControls():
                return 1
            win32gui.DestroyWindow(self.currentPageHwnd)
        #template = self.window.manager.dialog_parser.dialogs[self.page_ids[index]]
        import dlgcore
        self.currentPage = MakePropertyPage(self.page_placeholder_hwnd,
                                            self.window.manager,
                                            self.window.config,
                                            self.page_ids[index],
                                            3)
        self.currentPageHwnd = self.currentPage.CreateWindow()
        self.currentPageIndex = index
        self.changeControls()
        return 0
    def getNextPageIndex(self):
        next = self.getNextPage()
        if type(next)==type(0):
            return next
        # must be a dialog ID.
        for index, pid in enumerate(self.page_ids):
            if pid == next:
                return index
        assert 0, "No page '%s'" % next

    # methods to be overridden.  default implementation is simple sequential
    def getNextPage(self):
        return self.currentPageIndex+1
    def atFinish(self):
        return self.currentPageIndex==len(self.page_ids)-1
    def canGoNext(self):
        return True

# An implementation with the logic specific to our configuration wizard.
class ConfigureWizardProcessor(WizardButtonProcessor):
    def atFinish(self):
        index = self.currentPageIndex
        id = self.page_ids[index]
        return id.startswith("IDD_WIZARD_FINISHED")

    def canGoNext(self):
        # XXX - how to hook this in?  We really want this to be dynamic, as
        # options change - however, hooking WM_COMMAND at the parent doesn't
        # work how we want due to the property page being in the middle
        # (and then I gave up)
        index = self.currentPageIndex
        id = self.page_ids[index]
        config = self.window.config
        ok = True
        if id == 'IDD_WIZARD_FOLDERS_WATCH':
            ok = config.filter.watch_folder_ids
        elif id == 'IDD_WIZARD_FOLDERS_REST':
            # Check we have folders.
            ok = (config.wizard.spam_folder_name or config.filter.spam_folder_id) and \
               (config.wizard.unsure_folder_name or config.filter.unsure_folder_id)
        elif id == 'IDD_WIZARD_FOLDERS_TRAIN':
            ok = config.training.ham_folder_ids and \
                 config.training.spam_folder_ids
        elif id == 'IDD_WIZARD_TRAIN':
            # magically set to False when training finished (and back to True
            # if a folder ID is changed)
            ok = not self.window.config.wizard.need_train
        return ok

    def getNextPage(self):
        index = self.currentPageIndex
        id = self.page_ids[index]
        config = self.window.config
        print "GetNextPage with current", index, id
        if id == 'IDD_WIZARD_WELCOME':
            # Welcome page
            if config.wizard.preparation == 0: # unprepared
                return "IDD_WIZARD_TRAINING_IS_IMPORTANT"
            elif config.wizard.preparation == 1: # pre-prepared.
                return "IDD_WIZARD_FOLDERS_TRAIN"
            elif config.wizard.preparation == 2: # configure manually
                return "IDD_WIZARD_FINISHED_UNCONFIGURED"
            else:
                assert 0, "oops"
        elif id == 'IDD_WIZARD_TRAINING_IS_IMPORTANT':
            if config.wizard.will_train_later:
                # user wants to pre-sort and configure later.
                return 'IDD_WIZARD_FINISHED_TRAIN_LATER'
            return 'IDD_WIZARD_FOLDERS_WATCH'
        elif id == 'IDD_WIZARD_FOLDERS_TRAIN':
            return 'IDD_WIZARD_TRAIN'
        elif id == 'IDD_WIZARD_TRAIN':
            return 'IDD_WIZARD_FOLDERS_WATCH'
        elif id == 'IDD_WIZARD_FOLDERS_WATCH':
            return 'IDD_WIZARD_FOLDERS_REST'
        elif id == 'IDD_WIZARD_FOLDERS_REST':
            if config.wizard.preparation==1:
                return 'IDD_WIZARD_FINISHED_TRAINED'
            else:
                return 'IDD_WIZARD_FINISHED_UNTRAINED'

class WatchFolderIDProcessor(opt_processors.FolderIDProcessor):
    def __init__(self, window, control_ids,
                 option, option_include_sub = None,
                 use_fqn = True,
                 name_joiner = '\r\n'):
        opt_processors.FolderIDProcessor.__init__(self, window, control_ids,
                                                  option, option_include_sub,
                                                  use_fqn, name_joiner)

# For the wizard - folder "name" in an edit box, and ids used by
# browse dialog.  If ids None, "name" is assumed off the root, and created
# if necessary.
class EditableFolderIDProcessor(opt_processors.FolderIDProcessor):
    def __init__(self, window, control_ids,
                 option, option_folder_name, option_override = None,
                 use_fqn = False, name_joiner = "; "):
        self.button_id = control_ids[1]
        self.use_fqn = use_fqn
        self.name_joiner = name_joiner
        self.in_setting_name = False

        name_sect_name, name_sub_option_name = option_folder_name.split(".")

        self.option_folder_name = window.config.get_option(name_sect_name,
                                                            name_sub_option_name)
        if option_override:
            name_sect_name, name_sub_option_name = option_override.split(".")
            self.option_override = window.config.get_option(name_sect_name,
                                                            name_sub_option_name)
        else:
            self.option_override = None

        opt_processors.FolderIDProcessor.__init__(self, window, control_ids,
                                                  option, None,
                                                  use_fqn, name_joiner)

        # bit of a hack - if "Spam" is default and we have a training folder
        # then use that
        if self.GetOptionValue() is None and self.option_override:
            override = self.GetOptionValue(self.option_override)
            if override:
                # override is a multi-id value, we are single.
                self.SetOptionValue(override[0])
                self.SetOptionValue("", self.option_folder_name)

    def OnCommand(self, wparam, lparam):
        code = win32api.HIWORD(wparam)
        id = win32api.LOWORD(wparam)
        if id == self.control_id:
            if code==win32con.EN_CHANGE:
                if not self.in_setting_name:
                    # reset the folder IDs.
                    self.SetOptionValue(None)
                    # Set the folder name
                    hedit = win32gui.GetDlgItem(self.window.hwnd, id)
                    text = win32gui.GetWindowText(hedit)
                    self.SetOptionValue(text, self.option_folder_name)
        return opt_processors.FolderIDProcessor.OnCommand(self, wparam, lparam)

    def UpdateControl_FromValue(self):
        name_val = self.GetOptionValue(self.option_folder_name)
        id_val = self.GetOptionValue()
        self.in_setting_name = True
        if id_val:
            self.SetOptionValue("", self.option_folder_name)
            opt_processors.FolderIDProcessor.UpdateControl_FromValue(self)
        else:
            if name_val:
                win32gui.SetWindowText(self.GetControl(), name_val)
        self.in_setting_name = False

class TrainFolderIDProcessor(opt_processors.FolderIDProcessor):
    def SetOptionValue(self, value, option = None):
        self.window.config.wizard.need_train = True
        return opt_processors.FolderIDProcessor.SetOptionValue(self, value, option)

class WizAsyncProcessor(async_processor.AsyncCommandProcessor):
    def __init__(self, window, control_ids, func, start_text, stop_text, disable_ids):
        control_ids = [None] + control_ids
        async_processor.AsyncCommandProcessor.__init__(self, window, control_ids, func, start_text, stop_text, disable_ids)
    def Init(self):
        async_processor.AsyncCommandProcessor.Init(self)
        if self.window.config.wizard.need_train:
            self.StartProcess()
        else:
            self.SetStatusText("Training has already been completed - click Next to move to the next step.")
    def OnFinished(self, wparam, lparam):
        wasCancelled = wparam
        if not wasCancelled:
            self.window.config.wizard.need_train = False
        return async_processor.AsyncCommandProcessor.OnFinished(self, wparam, lparam)
