# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import sys
import os
import shutil

from gns3.qt import QtCore, QtWidgets, QtGui, QtNetwork, qslot
from gns3.controller import Controller
from gns3.local_server import LocalServer
from gns3.utils.interfaces import interfaces

from ..settings import DEFAULT_LOCAL_SERVER_HOST
from ..ui.setup_wizard_ui import Ui_SetupWizard
from ..version import __version__


import logging
log = logging.getLogger(__name__)


class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):

    """
    Base class for VM wizard.
    """

    def __init__(self, parent):

        super().__init__(parent)
        self.setupUi(self)
        self.adjustSize()

        self._gns3_vm_settings = {
            "enable": True,
            "headless": False,
            "when_exit": "stop",
            "engine": "vmware",
            "allocate_vcpus_ram": True,
            "vcpus": 1,
            "ram": 2048,
            "vmname": "GNS3 VM",
            "port": 80
        }

        self.setWizardStyle(QtWidgets.QWizard.WizardStyle.ModernStyle)
        if sys.platform.startswith("darwin"):
            # we want to see the cancel button on OSX
            self.setOptions(QtWidgets.QWizard.WizardOption.NoDefaultButton)

        self.uiLocalServerToolButton.clicked.connect(self._localServerBrowserSlot)

        self.uiGNS3VMDownloadLinkUrlLabel.setText("")
        self.uiRefreshPushButton.clicked.connect(self._refreshVMListSlot)
        self.uiVmwareRadioButton.clicked.connect(self._listVMwareVMsSlot)
        self.uiVirtualBoxRadioButton.clicked.connect(self._listVirtualBoxVMsSlot)
        settings = parent.settings()
        self.uiShowCheckBox.setChecked(settings["hide_setup_wizard"])

        # by default all radio buttons are unchecked
        self.uiVmwareRadioButton.setAutoExclusive(False)
        self.uiVirtualBoxRadioButton.setAutoExclusive(False)
        self.uiVmwareRadioButton.setChecked(False)
        self.uiVirtualBoxRadioButton.setChecked(False)

        # Mandatory fields
        self.uiLocalServerWizardPage.registerField("path*", self.uiLocalServerPathLineEdit)

        # load all available addresses
        for address in QtNetwork.QNetworkInterface.allAddresses():
            if address.protocol() in [QtNetwork.QAbstractSocket.NetworkLayerProtocol.IPv4Protocol, QtNetwork.QAbstractSocket.NetworkLayerProtocol.IPv6Protocol]:
                address_string = address.toString()
                if address_string.startswith("169.254") or address_string.startswith("fe80"):
                    # ignore link-local addresses, could not use https://doc.qt.io/qt-5/qhostaddress.html#isLinkLocal
                    # because it was introduced in Qt 5.11
                    continue
                self.uiLocalServerHostComboBox.addItem(address_string, address_string)

        self.uiLocalServerHostComboBox.addItem("localhost", "localhost")  # local host
        self.uiLocalServerHostComboBox.addItem("::", "::")  # all IPv6 addresses
        self.uiLocalServerHostComboBox.addItem("0.0.0.0", "0.0.0.0")  # all IPv4 addresses

        if sys.platform.startswith("linux"):
            self.uiLocalRadioButton.setChecked(True)
            self.uiLocalLabel.setText("Dependencies like Dynamips and Qemu must be manually installed")

        Controller.instance().connected_signal.connect(self._refreshLocalServerStatusSlot)
        Controller.instance().connection_failed_signal.connect(self._refreshLocalServerStatusSlot)

    def _localServerBrowserSlot(self):
        """
        Slot to open a file browser and select a local server.
        """

        filter = ""
        if sys.platform.startswith("win"):
            filter = "Executable (*.exe);;All files (*.*)"
        server_path = shutil.which("gns3server")
        path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Select the local server", server_path, filter)
        if not path:
            return

        self.uiLocalServerPathLineEdit.setText(path)

    def _listVMwareVMsSlot(self):
        """
        Slot to refresh the VMware VMs list.
        """

        download_url = "https://github.com/GNS3/gns3-gui/releases/download/v{version}/GNS3.VM.VMware.Workstation.{version}.zip".format(version=__version__)
        self.uiGNS3VMDownloadLinkUrlLabel.setText('The GNS3 VM can <a href="{download_url}">downloaded here</a>.<br>Import the VM in your virtualization software and hit refresh.'.format(download_url=download_url))
        self.uiVirtualBoxRadioButton.setChecked(False)
        from gns3.modules import VMware
        settings = VMware.instance().settings()
        if not os.path.exists(settings["vmrun_path"]):
            QtWidgets.QMessageBox.critical(self, "VMware", "VMware vmrun tool could not be found, VMware or the VIX API (required for VMware player) is probably not installed. You can download it from https://customerconnect.vmware.com/downloads/details?downloadGroup=PLAYER-1400-VIX1170&productId=687. After installation you need to restart GNS3.")
            return
        self._refreshVMListSlot()

    def _listVirtualBoxVMsSlot(self):
        """
        Slot to refresh the VirtualBox VMs list.
        """

        download_url = "https://github.com/GNS3/gns3-gui/releases/download/v{version}/GNS3.VM.VirtualBox.{version}.zip".format(version=__version__)
        self.uiGNS3VMDownloadLinkUrlLabel.setText('If you don\'t have the GNS3 Virtual Machine you can <a href="{download_url}">download it here</a>.<br>And import the VM in the virtualization software and hit refresh.'.format(download_url=download_url))
        self.uiVmwareRadioButton.setChecked(False)
        from gns3.modules import VirtualBox
        settings = VirtualBox.instance().settings()
        if not os.path.exists(settings["vboxmanage_path"]):
            QtWidgets.QMessageBox.critical(self, "VirtualBox", "VBoxManage could not be found, VirtualBox is probably not installed. After installation you need to restart GNS3.")
            return
        self._refreshVMListSlot()

    def _setPreferencesPane(self, dialog, name):
        """
        Finds the first child of the QTreeWidgetItem name.

        :param dialog: PreferencesDialog instance
        :param name: QTreeWidgetItem name

        :returns: current QWidget
        """

        pane = dialog.uiTreeWidget.findItems(name, QtCore.Qt.MatchFlag.MatchFixedString)[0]
        child_pane = pane.child(0)
        dialog.uiTreeWidget.setCurrentItem(child_pane)
        return dialog.uiStackedWidget.currentWidget()

    def _getSettingsCallback(self, result, error=False, **kwargs):
        if error:
            if "message" in result:
                log.error("Error while get gettings: {}".format(result["message"]))
            return
        self._gns3_vm_settings = result

    def initializePage(self, page_id):
        """
        Initialize Wizard pages.

        :param page_id: page identifier
        """

        super().initializePage(page_id)
        if self.page(page_id) == self.uiServerWizardPage:
            Controller.instance().setDisplayError(False)
            Controller.instance().get("/gns3vm", self._getSettingsCallback)
        elif self.page(page_id) == self.uiVMWizardPage:
            if self._GNS3VMSettings()["engine"] == "vmware":
                self.uiVmwareRadioButton.setChecked(True)
                self._listVMwareVMsSlot()
            elif self._GNS3VMSettings()["engine"] == "virtualbox":
                self.uiVirtualBoxRadioButton.setChecked(True)
                self._listVirtualBoxVMsSlot()
            self.uiCPUSpinBox.setValue(self._GNS3VMSettings()["vcpus"])
            self.uiRAMSpinBox.setValue(self._GNS3VMSettings()["ram"])

        elif self.page(page_id) == self.uiLocalServerWizardPage:
            local_server_settings = LocalServer.instance().localServerSettings()
            self.uiLocalServerPathLineEdit.setText(local_server_settings["path"])
            index = self.uiLocalServerHostComboBox.findData(local_server_settings["host"])
            if index != -1:
                self.uiLocalServerHostComboBox.setCurrentIndex(index)
            else:
                if self.uiVMRadioButton.isChecked():
                    # Try to bind with the IP address allocated for VMnet1
                    for interface in interfaces():
                        if "vmnet1" in interface["name"].lower():
                            index = self.uiLocalServerHostComboBox.findText(interface["ip_address"])
                            break
                else:
                    index = self.uiLocalServerHostComboBox.findText(DEFAULT_LOCAL_SERVER_HOST)

                if index != -1:
                    self.uiLocalServerHostComboBox.setCurrentIndex(index)

            self.uiLocalServerPortSpinBox.setValue(local_server_settings["port"])

        elif self.page(page_id) == self.uiRemoteControllerWizardPage:
            local_server_settings = LocalServer.instance().localServerSettings()
            if local_server_settings["host"] is None:
                self.uiRemoteMainServerHostLineEdit.setText(DEFAULT_LOCAL_SERVER_HOST)
                self.uiRemoteMainServerAuthCheckBox.setChecked(False)
                self.uiRemoteMainServerUserLineEdit.setText("")
                self.uiRemoteMainServerPasswordLineEdit.setText("")
            else:
                self.uiRemoteMainServerHostLineEdit.setText(local_server_settings["host"])
                self.uiRemoteMainServerAuthCheckBox.setChecked(local_server_settings["auth"])
                self.uiRemoteMainServerUserLineEdit.setText(local_server_settings["user"])
                self.uiRemoteMainServerPasswordLineEdit.setText(local_server_settings["password"])
            self.uiRemoteMainServerPortSpinBox.setValue(local_server_settings["port"])
        elif self.page(page_id) == self.uiLocalServerStatusWizardPage:
            self._refreshLocalServerStatusSlot()

        elif self.page(page_id) == self.uiSummaryWizardPage:
            self.uiSummaryTreeWidget.clear()
            if self.uiLocalRadioButton.isChecked():
                local_server_settings = LocalServer.instance().localServerSettings()
                self._addSummaryEntry("Server type:", "Local")
                self._addSummaryEntry("Path:", local_server_settings["path"])
                self._addSummaryEntry("Host:", local_server_settings["host"])
                self._addSummaryEntry("Port:", str(local_server_settings["port"]))
            elif self.uiRemoteControllerRadioButton.isChecked():
                local_server_settings = LocalServer.instance().localServerSettings()
                self._addSummaryEntry("Server type:", "Remote")
                self._addSummaryEntry("Host:", local_server_settings["host"])
                self._addSummaryEntry("Port:", str(local_server_settings["port"]))
                self._addSummaryEntry("User:", local_server_settings["user"])
            else:
                self._addSummaryEntry("Server type:", "GNS3 Virtual Machine")
                self._addSummaryEntry("VM engine:", self._GNS3VMSettings()["engine"].capitalize())
                self._addSummaryEntry("VM name:", self._GNS3VMSettings()["vmname"])
                self._addSummaryEntry("VM vCPUs:", str(self._GNS3VMSettings()["vcpus"]))
                self._addSummaryEntry("VM RAM:", str(self._GNS3VMSettings()["ram"]) + " MB")

    @qslot
    def _refreshLocalServerStatusSlot(self):
        """
        Refresh the local server status page
        """

        self.uiLocalServerTextEdit.clear()
        if Controller.instance().connected():
            self.uiLocalServerTextEdit.setText("Connection to the local GNS3 server has been successful!")
            Controller.instance().get("/gns3vm", self._getSettingsCallback)
        elif Controller.instance().connecting():
            self.uiLocalServerTextEdit.setText("Please wait connection to the GNS3 server...")
        else:
            local_server_settings = LocalServer.instance().localServerSettings()
            self.uiLocalServerTextEdit.setText("Connection to local server failed. Please try one of the following:\n\n- Make sure GNS3 is allowed to run by your firewall.\n- Go back and try to change the server host binding and/or the port\n- Check with a browser if you can connect to {protocol}://{host}:{port}.\n- Try to run {path} in a terminal to see if you have an error.".format(protocol=local_server_settings["protocol"], host=local_server_settings["host"], port=local_server_settings["port"], path=local_server_settings["path"]))

    def _GNS3VMSettings(self):
        return self._gns3_vm_settings

    def _setGNS3VMSettings(self, settings):
        Controller.instance().put("/gns3vm", self._saveSettingsCallback, settings, timeout=60 * 5)

    def _saveSettingsCallback(self, result, error=False, **kwargs):
        if error:
            if "message" in result:
                QtWidgets.QMessageBox.critical(self, "Save settings", "Error while saving settings: {}".format(result["message"]))
            return

    def _addSummaryEntry(self, name, value):

        item = QtWidgets.QTreeWidgetItem(self.uiSummaryTreeWidget, [name, value])
        item.setText(0, name)
        font = item.font(0)
        font.setBold(True)
        item.setFont(0, font)

    def validateCurrentPage(self):
        """
        Validates the settings.
        """

        Controller.instance().setDisplayError(True)
        if self.currentPage() == self.uiVMWizardPage:
            vmname = self.uiVMListComboBox.currentText()
            if vmname:
                # save the GNS3 VM settings
                vm_settings = self._GNS3VMSettings()
                vm_settings["enable"] = True
                vm_settings["vmname"] = vmname

                if self.uiVmwareRadioButton.isChecked():
                    vm_settings["engine"] = "vmware"
                elif self.uiVirtualBoxRadioButton.isChecked():
                    vm_settings["engine"] = "virtualbox"

                # set the vCPU count and RAM
                vpcus = self.uiCPUSpinBox.value()
                ram = self.uiRAMSpinBox.value()
                if ram < 1024:
                    QtWidgets.QMessageBox.warning(self, "GNS3 VM memory", "It is recommended to allocate a minimum of 1024 MB of memory to the GNS3 VM")
                vm_settings["vcpus"] = vpcus
                vm_settings["ram"] = ram

                self._setGNS3VMSettings(vm_settings)
            else:
                if not self.uiVmwareRadioButton.isChecked() and not self.uiVirtualBoxRadioButton.isChecked():
                    QtWidgets.QMessageBox.warning(self, "GNS3 VM", "Please select VMware or VirtualBox")
                else:
                    QtWidgets.QMessageBox.warning(self, "GNS3 VM", "Please select a VM. If no VM is listed, check if the GNS3 VM is correctly imported and press refresh.")
                return False
        elif self.currentPage() == self.uiLocalServerWizardPage:
            local_server_settings = LocalServer.instance().localServerSettings()
            local_server_settings["auto_start"] = True
            local_server_settings["path"] = self.uiLocalServerPathLineEdit.text().strip()
            local_server_settings["host"] = self.uiLocalServerHostComboBox.itemData(self.uiLocalServerHostComboBox.currentIndex())
            local_server_settings["port"] = self.uiLocalServerPortSpinBox.value()

            if not os.path.isfile(local_server_settings["path"]):
                QtWidgets.QMessageBox.critical(self, "Local server", "Could not find local server {}".format(local_server_settings["path"]))
                return False
            if not os.access(local_server_settings["path"], os.X_OK):
                QtWidgets.QMessageBox.critical(self, "Local server", "{} is not an executable".format(local_server_settings["path"]))
                return False

            LocalServer.instance().updateLocalServerSettings(local_server_settings)
            if not LocalServer.instance().localServerAutoStartIfRequired():
                return False

        elif self.currentPage() == self.uiRemoteControllerWizardPage:
            local_server_settings = LocalServer.instance().localServerSettings()
            local_server_settings["auto_start"] = False
            local_server_settings["host"] = self.uiRemoteMainServerHostLineEdit.text()
            local_server_settings["port"] = self.uiRemoteMainServerPortSpinBox.value()
            local_server_settings["protocol"] = "http"
            local_server_settings["user"] = self.uiRemoteMainServerUserLineEdit.text()
            local_server_settings["password"] = self.uiRemoteMainServerPasswordLineEdit.text()
            local_server_settings["auth"] = self.uiRemoteMainServerAuthCheckBox.isChecked()
            LocalServer.instance().updateLocalServerSettings(local_server_settings)

        elif self.currentPage() == self.uiSummaryWizardPage:
            if self.uiLocalRadioButton.isChecked():
                # deactivate the GNS3 VM if using the local server
                vm_settings = self._GNS3VMSettings()
                vm_settings["enable"] = False
                self._setGNS3VMSettings(vm_settings)

        elif self.currentPage() == self.uiLocalServerStatusWizardPage:
            if not Controller.instance().connected():
                return False

        return True

    def _refreshVMListSlot(self):
        """
        Refresh the list of VM available in VMware or VirtualBox.
        """

        if self.uiVmwareRadioButton.isChecked():
            Controller.instance().get("/gns3vm/engines/vmware/vms", self._getVMsFromServerCallback, progressText="Retrieving VMware VM list from server...")
        elif self.uiVirtualBoxRadioButton.isChecked():
            Controller.instance().get("/gns3vm/engines/virtualbox/vms", self._getVMsFromServerCallback, progressText="Retrieving VirtualBox VM list from server...")

    def _getVMsFromServerCallback(self, result, error=False, **kwargs):
        """
        Callback for getVMsFromServer.

        :param progress_dialog: QProgressDialog instance
        :param result: server response
        :param error: indicates an error (boolean)
        """

        if error:
            QtWidgets.QMessageBox.critical(self, "VM List", "{}".format(result["message"]))
        else:
            self.uiVMListComboBox.clear()
            for vm in result:
                self.uiVMListComboBox.addItem(vm["vmname"])

            index = self.uiVMListComboBox.findText(self._GNS3VMSettings()["vmname"])
            if index != -1:
                self.uiVMListComboBox.setCurrentIndex(index)
            else:
                index = self.uiVMListComboBox.findText("GNS3 VM")
                if index != -1:
                    self.uiVMListComboBox.setCurrentIndex(index)
                else:
                    QtWidgets.QMessageBox.critical(self, "GNS3 VM", "Could not find a VM named 'GNS3 VM', is it imported in VMware or VirtualBox?")

    def done(self, result):
        """
        This dialog is closed.

        :param result: ignored
        """

        Controller.instance().setDisplayError(True)
        settings = self.parentWidget().settings()
        if result:
            settings["hide_setup_wizard"] = True
        else:
            local_server_settings = LocalServer.instance().localServerSettings()
            LocalServer.instance().updateLocalServerSettings(local_server_settings)
            settings["hide_setup_wizard"] = not self.uiShowCheckBox.isChecked()
        self.parentWidget().setSettings(settings)
        super().done(result)

    def nextId(self):
        """
        Wizard rules!
        """

        current_id = self.currentId()
        if self.page(current_id) == self.uiLocalServerStatusWizardPage and not self.uiVMRadioButton.isChecked():
            return self._pageId(self.uiSummaryWizardPage)

        if self.page(current_id) == self.uiServerWizardPage and self.uiRemoteControllerRadioButton.isChecked():
            return self._pageId(self.uiRemoteControllerWizardPage)

        if self.page(current_id) == self.uiVMWizardPage:
            return self._pageId(self.uiSummaryWizardPage)
        return QtWidgets.QWizard.nextId(self)

    def _pageId(self, page):
        """
        Return id of the page
        """
        for id in self.pageIds():
            if self.page(id) == page:
                return id
        raise KeyError
