############################################################################
##
## Copyright (C) 2022 The Qt Company Ltd.
## Contact: https://www.qt.io/licensing/
##
## This file is part of the Qt for Python examples of the Qt Toolkit.
##
## $QT_BEGIN_LICENSE:BSD$
## You may use this file under the terms of the BSD license as follows:
##
## "Redistribution and use in source and binary forms, with or without
## modification, are permitted provided that the following conditions are
## met:
##   * Redistributions of source code must retain the above copyright
##     notice, this list of conditions and the following disclaimer.
##   * Redistributions in binary form must reproduce the above copyright
##     notice, this list of conditions and the following disclaimer in
##     the documentation and/or other materials provided with the
##     distribution.
##   * Neither the name of The Qt Company Ltd nor the names of its
##     contributors may be used to endorse or promote products derived
##     from this software without specific prior written permission.
##
##
## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
##
## $QT_END_LICENSE$
##
#############################################################################

from PySide6.QtCore import QByteArray, Qt
from PySide6.QtGui import QGuiApplication
from PySide6.QtNetwork import (QAbstractSocket, QHostAddress, QTcpServer,
                               QTcpSocket)
from PySide6.QtWidgets import (QDialog, QDialogButtonBox, QLabel, QMessageBox,
                               QProgressBar, QPushButton, QVBoxLayout)


class Dialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.total_bytes = 50 * 1024 * 1024  # 50 MB
        self.payload_size = 64 * 1024  # 64 KB

        self.bytes_to_write = 0
        self.bytes_written = 0
        self.bytes_received = 0

        self.client_progress_bar = QProgressBar()
        self.client_status_label = QLabel("Client ready")
        self.server_progress_bar = QProgressBar()
        self.server_status_label = QLabel("Server ready")

        self.start_button = QPushButton("&Start")
        self.quit_button = QPushButton("&Quit")

        self.button_box = QDialogButtonBox()
        self.button_box.addButton(self.start_button, QDialogButtonBox.ActionRole)
        self.button_box.addButton(self.quit_button, QDialogButtonBox.RejectRole)

        self.start_button.clicked.connect(self.start)
        self.quit_button.clicked.connect(self.close)

        self.tcp_server = QTcpServer()
        self.tcp_client = QTcpSocket()
        self.tcp_server.newConnection.connect(self.accept_connection)
        self.tcp_client.connected.connect(self.start_transfer)
        self.tcp_client.bytesWritten.connect(self.update_client_progress)
        self.tcp_client.errorOccurred.connect(self.display_error)

        main_layout = QVBoxLayout()
        main_layout.addWidget(self.client_progress_bar)
        main_layout.addWidget(self.client_status_label)
        main_layout.addWidget(self.server_progress_bar)
        main_layout.addWidget(self.server_status_label)
        main_layout.addStretch(1)
        main_layout.addSpacing(10)
        main_layout.addWidget(self.button_box)
        self.setLayout(main_layout)

        self.setWindowTitle("Loopback")

    def start(self):

        self.start_button.setEnabled(False)

        QGuiApplication.setOverrideCursor(Qt.WaitCursor)

        self.bytes_written = 0
        self.bytes_received = 0

        while not self.tcp_server.isListening() and not self.tcp_server.listen():
            ret: QMessageBox.StandardButton = QMessageBox.critical(
                self,
                "Loopback",
                f"Unable to start the test {self.tcp_server.errorString()}",
                QMessageBox.Retry | QMessageBox.Cancel,
            )
            if ret == QMessageBox.Cancel:
                return

        self.server_status_label.setText("Listening")
        self.client_status_label.setText("Connecting")
        self.tcp_client.connectToHost(QHostAddress.LocalHost, self.tcp_server.serverPort())

    def accept_connection(self):

        self.tcp_server_connection = self.tcp_server.nextPendingConnection()
        if not self.tcp_server_connection:
            self.server_status_label.setText("Error: got invalid pending connection")
            return

        self.tcp_server_connection.readyRead.connect(self.update_server_progress)
        self.tcp_server_connection.errorOccurred.connect(self.display_error)
        self.tcp_server_connection.disconnected.connect(self.tcp_server_connection.deleteLater)

        self.server_status_label.setText("Accepted connection")
        self.tcp_server.close()

    def start_transfer(self):

        # Called when the TCP client has connected to the loopback server
        self.bytes_to_write = self.total_bytes - self.tcp_client.write(
            QByteArray(self.payload_size, "@")
        )
        self.client_status_label.setText("Connected")

    def update_server_progress(self):

        self.bytes_received += self.tcp_server_connection.bytesAvailable()
        self.tcp_server_connection.readAll()

        self.server_progress_bar.setMaximum(self.total_bytes)
        self.server_progress_bar.setValue(self.bytes_received)
        self.server_status_label.setText(f"Received {self.bytes_received / (1024 ** 2)} MB")

        if self.bytes_received == self.total_bytes:

            self.tcp_server_connection.close()
            self.start_button.setEnabled(True)

            QGuiApplication.restoreOverrideCursor()

    def update_client_progress(self, num_bytes: int):

        # called when the TCP client has written some bytes
        self.bytes_written += num_bytes

        # only write more if not finished and when the Qt write buffer is below a certain size
        if self.bytes_to_write > 0 and self.tcp_client.bytesToWrite() <= 4 * self.payload_size:
            self.bytes_to_write -= self.tcp_client.write(
                QByteArray(min(self.bytes_to_write, self.payload_size), "@")
            )

        self.client_progress_bar.setMaximum(self.total_bytes)
        self.client_progress_bar.setValue(self.bytes_written)
        self.client_status_label.setText(f"Sent {self.bytes_written / (1024 ** 2)} MB")

    def display_error(self, socket_error: QAbstractSocket.SocketError):
        if socket_error == QAbstractSocket.RemoteHostClosedError:
            return

        QMessageBox.information(
            self,
            "Network error",
            f"The following error occurred: {self.tcp_client.errorString()}",
        )

        self.tcp_client.close()
        self.tcp_server.close()
        self.client_progress_bar.reset()
        self.server_progress_bar.reset()
        self.client_status_label.setText("Client ready")
        self.server_status_label.setText("Server ready")
        self.start_button.setEnabled(True)

        QGuiApplication.restoreOverrideCursor()
