/***************************************************************************
**
** Copyright (C) 2014 BlackBerry Limited. All rights reserved.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the QtBluetooth module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, 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$
**
****************************************************************************/

#include "pingpong.h"
#include <QDebug>
#ifdef Q_OS_ANDROID
#include <QtAndroid>
#endif

PingPong::PingPong():
    m_serverInfo(0), socket(0), discoveryAgent(0), interval(5), m_resultLeft(0), m_resultRight(0),
    m_showDialog(false), m_role(0), m_proportionX(0), m_proportionY(0), m_serviceFound(false)
{
    m_timer = new QTimer(this);
    connect(m_timer, &QTimer::timeout, this, &PingPong::update);
}

PingPong::~PingPong()
{
    delete m_timer;
    delete m_serverInfo;
    delete socket;
    delete discoveryAgent;
}

void PingPong::startGame()
{
    m_showDialog = false;
    emit showDialogChanged();
    //! [Start the game]
    if (m_role == 1)
        updateDirection();

    m_timer->start(50);
    //! [Start the game]
}

void PingPong::update()
{
    QByteArray size;
    // Server is only updating the coordinates
    //! [Updating coordinates]
    if (m_role == 1) {
        checkBoundaries();
        m_ballPreviousX = m_ballX;
        m_ballPreviousY = m_ballY;
        m_ballY = m_direction*(m_ballX+interval) - m_direction*m_ballX + m_ballY;
        m_ballX = m_ballX + interval;

        size.setNum(m_ballX);
        size.append(' ');
        QByteArray size1;
        size1.setNum(m_ballY);
        size.append(size1);
        size.append(' ');
        size1.setNum(m_leftBlockY);
        size.append(size1);
        size.append(" \n");
        socket->write(size.constData());
        emit ballChanged();
    }
    else if (m_role == 2) {
        size.setNum(m_rightBlockY);
        size.append(" \n");
        socket->write(size.constData());
    }
    //! [Updating coordinates]
}



void PingPong::setSize(const float &x, const float &y)
{
    m_boardWidth = x;
    m_boardHeight = y;
    m_targetX = m_boardWidth;
    m_targetY = m_boardHeight/2;
    m_ballPreviousX = m_ballX = m_boardWidth/2;
    m_ballPreviousY = m_ballY = m_boardHeight - m_boardWidth/54;
    emit ballChanged();
}

void PingPong::updateBall(const float &bX, const float &bY)
{
    m_ballX = bX;
    m_ballY = bY;
}

void PingPong::updateLeftBlock(const float &lY)
{
    m_leftBlockY = lY;
}

void PingPong::updateRightBlock(const float &rY)
{
    m_rightBlockY = rY;
}

void PingPong::checkBoundaries()
{
    float ballWidth = m_boardWidth/54;
    float blockSize = m_boardWidth/27;
    float blockHeight = m_boardHeight/5;
    //! [Checking the boundaries]
    if (((m_ballX + ballWidth) > (m_boardWidth - blockSize)) && ((m_ballY + ballWidth) < (m_rightBlockY + blockHeight))
            && (m_ballY > m_rightBlockY)) {
        m_targetY = 2 * m_ballY - m_ballPreviousY;
        m_targetX = m_ballPreviousX;
        interval = -5;
        updateDirection();
    }
    else if ((m_ballX < blockSize) && ((m_ballY + ballWidth) < (m_leftBlockY + blockHeight))
             && (m_ballY > m_leftBlockY)) {
        m_targetY = 2 * m_ballY - m_ballPreviousY;
        m_targetX = m_ballPreviousX;
        interval = 5;
        updateDirection();
    }
    else if (m_ballY < 0 || (m_ballY + ballWidth > m_boardHeight)) {
        m_targetY = m_ballPreviousY;
        m_targetX = m_ballX + interval;
        updateDirection();
    }
    //! [Checking the boundaries]
    else if ((m_ballX + ballWidth) > m_boardWidth) {
        m_resultLeft++;
        m_targetX = m_boardWidth;
        m_targetY = m_boardHeight/2;
        m_ballPreviousX = m_ballX = m_boardWidth/2;
        m_ballPreviousY = m_ballY = m_boardHeight - m_boardWidth/54;

        updateDirection();
        checkResult();
        QByteArray result;
        result.append("result ");
        QByteArray res;
        res.setNum(m_resultLeft);
        result.append(res);
        result.append(' ');
        res.setNum(m_resultRight);
        result.append(res);
        result.append(" \n");
        socket->write(result);
        qDebug() << result;
        emit resultChanged();
    }
    else if (m_ballX < 0) {
        m_resultRight++;
        m_targetX = 0;
        m_targetY = m_boardHeight/2;
        m_ballPreviousX = m_ballX = m_boardWidth/2;
        m_ballPreviousY = m_ballY = m_boardHeight - m_boardWidth/54;
        updateDirection();
        checkResult();
        QByteArray result;
        result.append("result ");
        QByteArray res;
        res.setNum(m_resultLeft);
        result.append(res);
        result.append(' ');
        res.setNum(m_resultRight);
        result.append(res);
        result.append(" \n");
        socket->write(result);
        emit resultChanged();
    }
}

void PingPong::checkResult()
{
    if (m_resultRight == 10 && m_role == 2) {
        setMessage("Game over. You win!");
        m_timer->stop();
    }
    else if (m_resultRight == 10 && m_role == 1) {
        setMessage("Game over. You lose!");
        m_timer->stop();
    }
    else if (m_resultLeft == 10 && m_role == 1) {
        setMessage("Game over. You win!");
        m_timer->stop();
    }
    else if (m_resultLeft == 10 && m_role == 2) {
        setMessage("Game over. You lose!");
        m_timer->stop();
    }
}

void PingPong::updateDirection()
{
    m_direction = (m_targetY - m_ballY)/(m_targetX - m_ballX);
}

void PingPong::startServer()
{
    setMessage(QStringLiteral("Starting the server"));
    //! [Starting the server]
    m_serverInfo = new QBluetoothServer(QBluetoothServiceInfo::RfcommProtocol, this);
    connect(m_serverInfo, &QBluetoothServer::newConnection,
            this, &PingPong::clientConnected);
    connect(m_serverInfo, QOverload<QBluetoothServer::Error>::of(&QBluetoothServer::error),
            this, &PingPong::serverError);
    const QBluetoothUuid uuid(serviceUuid);

    m_serverInfo->listen(uuid, QStringLiteral("PingPong server"));
    //! [Starting the server]
    setMessage(QStringLiteral("Server started, waiting for the client. You are the left player."));
    // m_role is set to 1 if it is a server
    m_role = 1;
    emit roleChanged();
}

void PingPong::startClient()
{
    //! [Searching for the service]
    discoveryAgent = new QBluetoothServiceDiscoveryAgent(QBluetoothAddress());

    connect(discoveryAgent, &QBluetoothServiceDiscoveryAgent::serviceDiscovered,
            this, &PingPong::addService);
    connect(discoveryAgent, &QBluetoothServiceDiscoveryAgent::finished,
            this, &PingPong::done);
    connect(discoveryAgent, QOverload<QBluetoothServiceDiscoveryAgent::Error>::of(&QBluetoothServiceDiscoveryAgent::error),
            this, &PingPong::serviceScanError);
#ifdef Q_OS_ANDROID //see QTBUG-61392
    if (QtAndroid::androidSdkVersion() >= 23)
        discoveryAgent->setUuidFilter(QBluetoothUuid(androidUuid));
    else
        discoveryAgent->setUuidFilter(QBluetoothUuid(serviceUuid));
#else
    discoveryAgent->setUuidFilter(QBluetoothUuid(serviceUuid));
#endif
    discoveryAgent->start(QBluetoothServiceDiscoveryAgent::FullDiscovery);

    //! [Searching for the service]
    setMessage(QStringLiteral("Starting server discovery. You are the right player"));
    // m_role is set to 2 if it is a client
    m_role = 2;
    emit roleChanged();
}

void PingPong::clientConnected()
{
    //! [Initiating server socket]
    if (!m_serverInfo->hasPendingConnections()) {
        setMessage("FAIL: expected pending server connection");
        return;
    }
    socket = m_serverInfo->nextPendingConnection();
    if (!socket)
        return;
    socket->setParent(this);
    connect(socket, &QBluetoothSocket::readyRead,
            this, &PingPong::readSocket);
    connect(socket, &QBluetoothSocket::disconnected,
            this, &PingPong::clientDisconnected);
    connect(socket, QOverload<QBluetoothSocket::SocketError>::of(&QBluetoothSocket::error),
            this, &PingPong::socketError);

    //! [Initiating server socket]
    setMessage(QStringLiteral("Client connected."));

    QByteArray size;
    size.setNum(m_boardWidth);
    size.append(' ');
    QByteArray size1;
    size1.setNum(m_boardHeight);
    size.append(size1);
    size.append(" \n");
    socket->write(size.constData());

}

void PingPong::clientDisconnected()
{
    setMessage(QStringLiteral("Client disconnected"));
    m_timer->stop();
}

void PingPong::socketError(QBluetoothSocket::SocketError error)
{
    Q_UNUSED(error);
    m_timer->stop();
}

void PingPong::serverError(QBluetoothServer::Error error)
{
    Q_UNUSED(error);
    m_timer->stop();
}

void PingPong::done()
{
    qDebug() << "Service scan done";
    if (!m_serviceFound)
        setMessage("PingPong service not found");
}

void PingPong::addService(const QBluetoothServiceInfo &service)
{
    setMessage("Service found. Setting parameters...");
    //! [Connecting the socket]
    socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol);
    socket->connectToService(service);

    connect(socket, &QBluetoothSocket::readyRead, this, &PingPong::readSocket);
    connect(socket, &QBluetoothSocket::connected, this, &PingPong::serverConnected);
    connect(socket, &QBluetoothSocket::disconnected, this, &PingPong::serverDisconnected);
    //! [Connecting the socket]
    m_serviceFound = true;
}

void PingPong::serviceScanError(QBluetoothServiceDiscoveryAgent::Error error)
{
    setMessage(QStringLiteral("Scanning error") + error);
}

bool PingPong::showDialog() const
{
    return m_showDialog;
}

QString PingPong::message() const
{
    return m_message;
}

void PingPong::serverConnected()
{
    setMessage("Server Connected");
    QByteArray size;
    size.setNum(m_boardWidth);
    size.append(' ');
    QByteArray size1;
    size1.setNum(m_boardHeight);
    size.append(size1);
    size.append(" \n");
    socket->write(size.constData());
}

void PingPong::serverDisconnected()
{
    setMessage("Server Disconnected");
    m_timer->stop();
}

void PingPong::readSocket()
{
    if (!socket)
        return;
    const char sep = ' ';
    QByteArray line;
    while (socket->canReadLine()) {
        line = socket->readLine();
        //qDebug() << QString::fromUtf8(line.constData(), line.length());
        if (line.contains("result")) {
            QList<QByteArray> result = line.split(sep);
            if (result.size() > 2) {
                QByteArray leftSide = result.at(1);
                QByteArray rightSide = result.at(2);
                m_resultLeft = leftSide.toInt();
                m_resultRight = rightSide.toInt();
                emit resultChanged();
                checkResult();
            }
        }
    }
    if ((m_proportionX == 0 || m_proportionY == 0)) {
        QList<QByteArray> boardSize = line.split(sep);
        if (boardSize.size() > 1) {
            QByteArray boardWidth = boardSize.at(0);
            QByteArray boardHeight = boardSize.at(1);
            m_proportionX = m_boardWidth/boardWidth.toFloat();
            m_proportionY = m_boardHeight/boardHeight.toFloat();
            setMessage("Screen adjusted. Get ready!");
            QTimer::singleShot(3000, this, SLOT(startGame()));
        }
    }
    else if (m_role == 1) {
        QList<QByteArray> boardSize = line.split(sep);
        if (boardSize.size() > 1) {
            QByteArray rightBlockY = boardSize.at(0);
            m_rightBlockY = m_proportionY * rightBlockY.toFloat();
            emit rightBlockChanged();
        }
    }
    else if (m_role == 2) {
        QList<QByteArray> boardSize = line.split(sep);
        if (boardSize.size() > 2) {
            QByteArray ballX = boardSize.at(0);
            QByteArray ballY = boardSize.at(1);
            QByteArray leftBlockY = boardSize.at(2);
            m_ballX = m_proportionX * ballX.toFloat();
            m_ballY = m_proportionY * ballY.toFloat();
            m_leftBlockY = m_proportionY * leftBlockY.toFloat();
            emit leftBlockChanged();
            emit ballChanged();
        }
    }
}

void PingPong::setMessage(const QString &message)
{
    m_showDialog = true;
    m_message = message;
    emit showDialogChanged();
}

int PingPong::role() const
{
    return m_role;
}

int PingPong::leftResult() const
{
    return m_resultLeft;
}

int PingPong::rightResult() const
{
    return m_resultRight;
}

float PingPong::ballX() const
{
    return m_ballX;
}

float PingPong::ballY() const
{
    return m_ballY;
}


float PingPong::leftBlockY() const
{
    return m_leftBlockY;
}

float PingPong::rightBlockY() const
{
    return m_rightBlockY;
}
