# Copyright (C) 2020 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

from PySide6.QtCore import Qt, Slot, QUrl, QDateTime, QByteArray
from PySide6.QtNetwork import QNetworkInterface, QHostInfo
from PySide6.QtWidgets import QMainWindow, QMessageBox, QDialog, QFileDialog
from PySide6.QtCoap import QCoapClient, QCoapOption, QCoapResource, QtCoap, QCoapMessage, QCoapRequest

from ui_mainwindow import Ui_MainWindow as ui
from optiondialog import OptionDialog

class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        self._ui = ui()
        self._client = QCoapClient()
        self._options = []  # QVector<QCoapOption>
        self._currentData = ""
        self._client = QCoapClient(QtCoap.SecurityMode.NoSecurity, self)

        self._client.finished.connect(self.onFinished)
        self._client.error.connect(self.onError)

        self._ui.setupUi(self)

        self._ui.methodComboBox.addItem("Get", QtCoap.Method.Get)
        self._ui.methodComboBox.addItem("Put", QtCoap.Method.Put)
        self._ui.methodComboBox.addItem("Post", QtCoap.Method.Post)
        self._ui.methodComboBox.addItem("Delete", QtCoap.Method.Delete)

        self.fillHostSelector()
        self._ui.hostComboBox.setFocus()

    @Slot()
    def fillHostSelector(self):
        networkInterfaces = QNetworkInterface.allInterfaces()
        for interface in networkInterfaces:
            for address in interface.addressEntries():
                self._ui.hostComboBox.addItem(address.ip().toString())

    @Slot()
    def addMessage(self, message, isError=False):
        try:
            _message = message.data().decode("utf-8")
        except:
            _message = message
        content = "--------------- {} ---------------\n{}\n\n\n".format(
            QDateTime.currentDateTime().toString(), _message)
        self._ui.textEdit.setTextColor(Qt.red if isError else Qt.black)
        self._ui.textEdit.insertPlainText(content)
        self._ui.textEdit.ensureCursorVisible()

    @Slot()
    def onFinished(self, reply):
        if reply.errorReceived() == QtCoap.Error.Ok:
            self.addMessage(reply.message().payload())

    def errorMessage(self, errorCode):
        error = QtCoap.Error(errorCode)
        return "Request failed with error: {}\n".format(error)

    @Slot()
    def onError(self, reply, error):
        errorCode = reply.errorReceived() if reply else error
        self.addMessage(self.errorMessage(errorCode), isError=True)

    @Slot()
    def onDiscovered(self, reply, resources):
        if reply.errorReceived() != QtCoap.Error.Ok:
            return

        message = ""
        for resource in resources:
            self._ui.resourceComboBox.addItem(resource.path())
            message += 'Discovered resource: "{}" on path {}\n'.format(resource.title(), resource.path())
        self.addMessage(message)

    @Slot()
    def onNotified(self, reply, message):
        if reply.errorReceived() == QtCoap.Error.Ok:
            self.addMessage("Received observe notification with payload: {}".format(message.payload().data().decode("utf-8")))

    def tryToResolveHostName(self, hostname):
        hostinfo = QHostInfo.fromName(hostname)
        if len(hostinfo.addresses()) > 0:
            return hostinfo.addresses()[0].toString()
        return hostname

    @Slot()
    def on_runButton_clicked(self):
        msgType = QCoapMessage.Type.Confirmable if self._ui.msgTypeCheckBox.isChecked() else QCoapMessage.Type.NonConfirmable
        url = QUrl()
        url.setHost(self.tryToResolveHostName(self._ui.hostComboBox.currentText()))
        url.setPort(self._ui.portSpinBox.value())
        url.setPath(self._ui.resourceComboBox.currentText())

        request = QCoapRequest(url, msgType)
        for option in self._options:
            request.addOption(option)
        self._options.clear()

        method = QtCoap.Method(self._ui.methodComboBox.currentData(Qt.UserRole))
        if method == QtCoap.Method.Get:
            self._client.get(request)
        elif method == QtCoap.Method.Put:
            self._client.put(request, QByteArray(self._currentData.encode("utf-8")))
        elif method == QtCoap.Method.Post:
            self._client.post(request, QByteArray(self._currentData.encode("utf-8")))
        elif method == QtCoap.Method.Delete:
            self._client.deleteResource(request)
        self._currentData = ""

    @Slot()
    def on_discoverButton_clicked(self):
        url = QUrl()
        url.setHost(self.tryToResolveHostName(self._ui.hostComboBox.currentText()))
        url.setPort(self._ui.portSpinBox.value())

        self.discoverReply = self._client.discover(url, self._ui.discoveryPathEdit.text())
        if self.discoverReply:
            self.discoverReply.discovered.connect(self.onDiscovered)
        else:
            QMessageBox.critical(self, "Error", "Something went wrong, discovery request failed.");

    @Slot()
    def on_observeButton_clicked(self):
        url = QUrl()
        url.setHost(self.tryToResolveHostName(self._ui.hostComboBox.currentText()))
        url.setPort(self._ui.portSpinBox.value())
        url.setPath(self._ui.resourceComboBox.currentText())

        self.observerReply = self._client.observe(url)
        if not self.observerReply:
            QMessageBox.critical(self, "Error", "Something went wrong, observe request failed.")
            return

        self.observerReply.notified.connect(self.onNotified)

        def on_cancelObserveButton_clicked():
            self._client.cancelObserve(url)
            self._ui.cancelObserveButton.setEnabled(False)


        self._ui.cancelObserveButton.setEnabled(True)
        self._ui.cancelObserveButton.clicked.connect(on_cancelObserveButton_clicked)

    @Slot()
    def on_addOptionsButton_clicked(self):
        dialog = OptionDialog()
        if dialog.exec() == QDialog.Accepted:
            self._options = dialog.options()

    @Slot()
    def on_contentButton_clicked(self):
        dialog = QFileDialog(self)
        dialog.setFileMode(QFileDialog.AnyFile)
        if not dialog.exec():
            return

        fileName = dialog.selectedFiles().back()
        file = QFile(fileName);
        if not file.open(QIODevice.ReadOnly):
            QMessageBox.critical(self, "Error", "Failed to read from file {}".format(fileName))
            return
        self._currentData = file.readAll()

    @Slot(str)
    def on_resourceComboBox_editTextChanged(self, text):
        self._ui.observeButton.setEnabled(len(text) != 0)

    @Slot(int)
    def on_methodComboBox_currentIndexChanged(self, index):
        method = QtCoap.Method(self._ui.methodComboBox.currentData(Qt.UserRole))
        self._ui.contentButton.setEnabled(method == QtCoap.Method.Put or method == QtCoap.Method.Post)
