#
# This file is part of the LibreOffice project.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This file incorporates work covered by the following license notice:
#
#   Licensed to the Apache Software Foundation (ASF) under one or more
#   contributor license agreements. See the NOTICE file distributed
#   with this work for additional information regarding copyright
#   ownership. The ASF licenses this file to you under the Apache
#   License, Version 2.0 (the "License"); you may not use this file
#   except in compliance with the License. You may obtain a copy of
#   the License at http://www.apache.org/licenses/LICENSE-2.0 .
#
import uno
import traceback
from abc import ABCMeta, abstractmethod
from .UnoDialog2 import UnoDialog2, Desktop, PropertyNames, UIConsts, \
    ItemListenerProcAdapter
from ..common.HelpIds import HelpIds
from ..common.FileAccess import FileAccess

from com.sun.star.lang import NoSuchMethodException
from com.sun.star.frame import TerminationVetoException
from com.sun.star.awt.PushButtonType import HELP, STANDARD

import sys, os

if sys.version_info < (3,4):
    import imp
    imp.load_source('strings', os.path.join(os.path.dirname(__file__), '../common/strings.hrc'))
    import strings
elif sys.version_info < (3,7):
    # imp is deprecated since Python v.3.4
    from importlib.machinery import SourceFileLoader
    SourceFileLoader('strings', os.path.join(os.path.dirname(__file__), '../common/strings.hrc')).load_module()
    import strings
else:
    # have to jump through hoops since 3.7, partly because python does not like loading modules that do have a .py extension
    import importlib
    import importlib.util
    import importlib.machinery
    module_name = 'strings'
    path = os.path.join(os.path.dirname(__file__), '../common/strings.hrc')
    spec = importlib.util.spec_from_loader(
        module_name,
        importlib.machinery.SourceFileLoader(module_name, path)
    )
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    sys.modules[module_name] = module
    strings = module

class WizardDialog(UnoDialog2):

    __metaclass__ = ABCMeta

    __NEXT_ACTION_PERFORMED = "gotoNextAvailableStep"
    __BACK_ACTION_PERFORMED = "gotoPreviousAvailableStep"
    __FINISH_ACTION_PERFORMED = "finishWizard_1"
    __CANCEL_ACTION_PERFORMED = "cancelWizard_1"
    __HELP_ACTION_PERFORMED = None

    '''
    Creates a new instance of WizardDialog
    the hid is used as following :
    "HID:(hid)"   - the dialog
    "HID:(hid+1)  - the help button
    "HID:(hid+2)" - the back button
    "HID:(hid+3)" - the next button
    "HID:(hid+4)" - the create button
    "HID:(hid+5)" - the cancel button
    @param xMSF
    @param hid_
    '''

    def __init__(self, xMSF, hid_):
        super(WizardDialog,self).__init__(xMSF)
        self.__hid = hid_
        self.iButtonWidth = 50
        self.nNewStep = 1
        self.nOldStep = 1
        self.nMaxStep = 1
        self.bTerminateListenermustberemoved = True
        self.oRoadmap = None
        self.terminateListener = None

    def activate(self):
        try:
            self.xUnoDialog.toFront()
        except Exception:
            pass
            # do nothing;

    def itemStateChanged(self, itemEvent):
        try:
            self.nNewStep = itemEvent.ItemId
            self.nOldStep = int(self.xDialogModel.Step)
            if self.nNewStep != self.nOldStep:
                self.switchToStep()

        except Exception:
            traceback.print_exc()

    def setDialogProperties(self, closeable, height, moveable, position_x,
        position_Y, step, tabIndex, title, width):
        uno.invoke(self.xDialogModel, "setPropertyValues",
             ((PropertyNames.PROPERTY_CLOSEABLE,
                PropertyNames.PROPERTY_HEIGHT,
                PropertyNames.PROPERTY_MOVEABLE,
                PropertyNames.PROPERTY_POSITION_X,
                PropertyNames.PROPERTY_POSITION_Y,
                PropertyNames.PROPERTY_STEP,
                PropertyNames.PROPERTY_TABINDEX,
                PropertyNames.PROPERTY_TITLE,
                PropertyNames.PROPERTY_WIDTH),
             (closeable, height, moveable, position_x, position_Y, step,
                tabIndex, title, width)))

    def setRoadmapInteractive(self, _bInteractive):
        self.oRoadmap.Activated = _bInteractive

    def setRoadmapComplete(self, bComplete):
        self.oRoadmap.Complete = bComplete

    def isRoadmapComplete(self):
        try:
            return bool(self.oRoadmap.Complete)
        except Exception:
            traceback.print_exc()
            return False

    def setCurrentRoadmapItemID(self, ID):
        if self.oRoadmap is not None:
            nCurItemID = self.getCurrentRoadmapItemID()
            if nCurItemID != ID:
                self.oRoadmap.CurrentItemID = ID

    def getCurrentRoadmapItemID(self):
        try:
            return int(self.oRoadmap.CurrentItemID)
        except Exception:
            traceback.print_exc()
            return -1


    def initializePaths(self):
        xPropertySet = \
            self.xMSF.createInstance("com.sun.star.util.PathSettings")
        self.sUserTemplatePath = \
            xPropertySet.getPropertyValue("Template_writable")
        myFA = FileAccess(self.xMSF)
        aInternalPaths = xPropertySet.getPropertyValue("Template_internal")
        self.sTemplatePath = ""
        for path in aInternalPaths:
            if myFA.exists(path + "/wizard", False):
                self.sTemplatePath = path
                break
        if self.sTemplatePath == "":
            raise Exception("could not find wizard templates")

    def addRoadmap(self):
        try:
            iDialogHeight = self.xDialogModel.Height
            # the roadmap control has got no real TabIndex ever
            # that is not correct, but changing this would need time,
            # so it is used without TabIndex as before

            xRoadmapControl = self.insertControlModel(
                "com.sun.star.awt.UnoControlRoadmapModel",
                "rdmNavi",
                (PropertyNames.PROPERTY_HEIGHT,
                    PropertyNames.PROPERTY_POSITION_X,
                    PropertyNames.PROPERTY_POSITION_Y,
                    PropertyNames.PROPERTY_STEP,
                    PropertyNames.PROPERTY_TABINDEX, "Tabstop",
                    PropertyNames.PROPERTY_WIDTH),
                ((iDialogHeight - 26), 0, 0, 0,
                    0, True, 85))
            self.oRoadmap = xRoadmapControl.Model
            method = getattr(self, "itemStateChanged")
            xRoadmapControl.addItemListener(
                ItemListenerProcAdapter(method))

            self.oRoadmap.Text = strings.RID_COMMON_START_16
        except NoSuchMethodException:
            from com.sun.star.awt.VclWindowPeerAttribute import OK
            from .SystemDialog import SystemDialog
            sError = "The files required could not be found.\n" + \
                "Please start the LibreOffice Setup and choose 'Repair'."
            SystemDialog.showMessageBox(super().xMSF, "ErrorBox", OK, sError)
        except Exception:
            traceback.print_exc()

    def getRoadmapItemByID(self, _ID):
        try:
            getByIndex = self.oRoadmap.getByIndex
            for i in list(range(self.oRoadmap.Count)):
                CurRoadmapItem = getByIndex(i)
                CurID = int(CurRoadmapItem.ID)
                if CurID == _ID:
                    return CurRoadmapItem

            return None
        except Exception:
            traceback.print_exc()
            return None

    def switchToStep(self,_nOldStep=None, _nNewStep=None):
        if _nOldStep is not None and _nNewStep is not None:
            self.nOldStep = _nOldStep
            self.nNewStep = _nNewStep

        self.leaveStep(self.nOldStep, self.nNewStep)
        if self.nNewStep != self.nOldStep:
            if self.nNewStep == self.nMaxStep:
                self.xDialogModel.btnWizardNext.DefaultButton = False
                self.xDialogModel.btnWizardFinish.DefaultButton = True
            else:
                self.xDialogModel.btnWizardNext.DefaultButton = True
                self.xDialogModel.btnWizardFinish.DefaultButton = False

            self.changeToStep(self.nNewStep)
            self.enterStep(self.nOldStep, self.nNewStep)
            return True

        return False

    @abstractmethod
    def leaveStep(self, nOldStep, nNewStep):
        pass

    @abstractmethod
    def enterStep(self, nOldStep, nNewStep):
        pass

    def changeToStep(self, nNewStep):
        self.xDialogModel.Step = nNewStep
        self.setCurrentRoadmapItemID(nNewStep)
        self.enableNextButton(self.getNextAvailableStep() > 0)
        self.enableBackButton(nNewStep != 1)

    def drawNaviBar(self):
        try:
            curtabindex = UIConsts.SOFIRSTWIZARDNAVITABINDEX
            iButtonWidth = self.iButtonWidth
            iButtonHeight = 14
            iCurStep = 0
            iDialogHeight = self.xDialogModel.Height
            iDialogWidth = self.xDialogModel.Width
            iHelpPosX = 8
            iBtnPosY = iDialogHeight - iButtonHeight - 6
            iCancelPosX = iDialogWidth - self.iButtonWidth - 6
            iFinishPosX = iCancelPosX - 6 - self.iButtonWidth
            iNextPosX = iFinishPosX - 6 - self.iButtonWidth
            iBackPosX = iNextPosX - 3 - self.iButtonWidth
            self.insertControlModel(
                "com.sun.star.awt.UnoControlFixedLineModel",
                "lnNaviSep",
                (PropertyNames.PROPERTY_HEIGHT, "Orientation",
                    PropertyNames.PROPERTY_POSITION_X,
                    PropertyNames.PROPERTY_POSITION_Y,
                    PropertyNames.PROPERTY_STEP,
                    PropertyNames.PROPERTY_WIDTH),
                (1, 0, 0, iDialogHeight - 26, iCurStep, iDialogWidth))
            self.insertControlModel(
                "com.sun.star.awt.UnoControlFixedLineModel",
                "lnRoadSep",
                (PropertyNames.PROPERTY_HEIGHT,
                    "Orientation",
                    PropertyNames.PROPERTY_POSITION_X,
                    PropertyNames.PROPERTY_POSITION_Y,
                    PropertyNames.PROPERTY_STEP,
                    PropertyNames.PROPERTY_WIDTH),
                (iBtnPosY - 6, 1, 85, 0, iCurStep, 1))
            propNames = (PropertyNames.PROPERTY_ENABLED,
                PropertyNames.PROPERTY_HEIGHT,
                PropertyNames.PROPERTY_HELPURL,
                PropertyNames.PROPERTY_LABEL,
                PropertyNames.PROPERTY_POSITION_X,
                PropertyNames.PROPERTY_POSITION_Y,
                "PushButtonType",
                PropertyNames.PROPERTY_STEP,
                PropertyNames.PROPERTY_TABINDEX,
                PropertyNames.PROPERTY_WIDTH)
            self.xDialogModel.HelpURL = HelpIds.getHelpIdString(self.__hid)
            self.insertButton("btnWizardHelp",
                WizardDialog.__HELP_ACTION_PERFORMED,
                (PropertyNames.PROPERTY_ENABLED,
                    PropertyNames.PROPERTY_HEIGHT,
                    PropertyNames.PROPERTY_LABEL,
                    PropertyNames.PROPERTY_POSITION_X,
                    PropertyNames.PROPERTY_POSITION_Y,
                    "PushButtonType",
                    PropertyNames.PROPERTY_STEP,
                    PropertyNames.PROPERTY_TABINDEX,
                    PropertyNames.PROPERTY_WIDTH),
                (True, iButtonHeight,
                    strings.RID_COMMON_START_15,
                    iHelpPosX, iBtnPosY,
                    uno.Any("short",HELP), iCurStep,
                    uno.Any("short",(curtabindex + 1)), iButtonWidth), self)
            self.insertButton("btnWizardBack",
                WizardDialog.__BACK_ACTION_PERFORMED, propNames,
                (False, iButtonHeight, HelpIds.getHelpIdString(self.__hid + 2),
                    strings.RID_COMMON_START_13,
                    iBackPosX, iBtnPosY, uno.Any("short",STANDARD), iCurStep,
                    uno.Any("short",(curtabindex + 1)), iButtonWidth), self)
            self.insertButton("btnWizardNext",
                WizardDialog.__NEXT_ACTION_PERFORMED, propNames,
                (True, iButtonHeight, HelpIds.getHelpIdString(self.__hid + 3),
                    strings.RID_COMMON_START_14,
                    iNextPosX, iBtnPosY, uno.Any("short",STANDARD), iCurStep,
                    uno.Any("short",(curtabindex + 1)), iButtonWidth), self)
            self.insertButton("btnWizardFinish",
                WizardDialog.__FINISH_ACTION_PERFORMED, propNames,
                (True, iButtonHeight, HelpIds.getHelpIdString(self.__hid + 4),
                    strings.RID_COMMON_START_12,
                        iFinishPosX, iBtnPosY, uno.Any("short",STANDARD),
                        iCurStep,
                        uno.Any("short",(curtabindex + 1)),
                        iButtonWidth), self)
            self.insertButton("btnWizardCancel",
                WizardDialog.__CANCEL_ACTION_PERFORMED, propNames,
                (True, iButtonHeight, HelpIds.getHelpIdString(self.__hid + 5),
                    strings.RID_COMMON_START_11,
                    iCancelPosX, iBtnPosY, uno.Any("short",STANDARD), iCurStep,
                    uno.Any("short",(curtabindex + 1)),
                    iButtonWidth), self)
            self.xDialogModel.btnWizardNext.DefaultButton = True
        except Exception:
            traceback.print_exc()

    def insertRoadMapItems(self, items, enabled):
        for index, item in enumerate(items):
            try:
                oRoadmapItem = self.oRoadmap.createInstance()
                oRoadmapItem.Label = item
                oRoadmapItem.Enabled = enabled[index]
                oRoadmapItem.ID = index + 1
                self.oRoadmap.insertByIndex(index, oRoadmapItem)
            except Exception:
                traceback.print_exc()

    def enableBackButton(self, enabled):
        self.xDialogModel.btnWizardBack.Enabled = enabled

    def enableNextButton(self, enabled):
        self.xDialogModel.btnWizardNext.Enabled = enabled

    def enableFinishButton(self, enabled):
        self.xDialogModel.btnWizardFinish.Enabled = enabled

    def isStepEnabled(self, _nStep):
        try:
            xRoadmapItem = self.getRoadmapItemByID(_nStep)
            # Todo: In this case an exception should be thrown
            if xRoadmapItem is None:
                return False
            bIsEnabled = bool(xRoadmapItem.Enabled)
            return bIsEnabled
        except Exception:
            traceback.print_exc()
            return False

    def gotoPreviousAvailableStep(self):
        try:
            if self.nNewStep > 1:
                self.nOldStep = self.nNewStep
                self.nNewStep -= 1
                while self.nNewStep > 0:
                    bIsEnabled = self.isStepEnabled(self.nNewStep)
                    if bIsEnabled:
                        break;

                    self.nNewStep -= 1
                if (self.nNewStep == 0):
                    self.nNewStep = self.nOldStep
                self.switchToStep()
        except Exception:
            traceback.print_exc()

    def getNextAvailableStep(self):
        if self.isRoadmapComplete():
            i = self.nNewStep + 1
            while i <= self.nMaxStep:
                if self.isStepEnabled(i):
                    return i
                i += 1
        return -1

    def gotoNextAvailableStep(self):
        try:
            self.nOldStep = self.nNewStep
            self.nNewStep = self.getNextAvailableStep()
            if self.nNewStep > -1:
                self.switchToStep()
        except Exception:
            traceback.print_exc()

    @abstractmethod
    def finishWizard(self):
        pass

    def finishWizard_1(self):
        '''This function will call
        if the finish button is pressed on the UI'''
        try:
            self.enableFinishButton(False)
            success = False
            try:
                success = self.finishWizard()
            finally:
                if not success:
                    self.enableFinishButton(True)

            if success:
                self.removeTerminateListener()
        except Exception:
             traceback.print_exc()

    def getCurrentStep(self):
        try:
            return int(self.xDialogModel.Step)
        except Exception:
            traceback.print_exc()
            return -1

    def cancelWizard(self):
        #can be overwritten by extending class
        self.xUnoDialog.endExecute()

    def removeTerminateListener(self):
        if self.bTerminateListenermustberemoved:
            Desktop.getDesktop(self.xMSF).removeTerminateListener(self.terminateListener)
            self.bTerminateListenermustberemoved = False

    '''
    called by the cancel button and
    by the window hidden event.
    if this method was not called before,
    perform a cancel.
    '''

    def cancelWizard_1(self):
        try:
            self.cancelWizard()
            self.removeTerminateListener()
        except Exception:
            traceback.print_exc()

    def windowHidden(self):
        self.cancelWizard_1()

    def queryTermination(self):
        self.activate()
        raise TerminationVetoException()

    def disposing(self, arg0):
        self.cancelWizard_1()

    def optCreateFromTemplateItemChanged(self):
        self.bEditTemplate = False

    def optMakeChangesItemChanged(self):
        self.bEditTemplate = True
