/* This file is part of Clementine.
   Copyright 2013, Andreas Muttscheller <asfa194@gmail.com>

   Clementine 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.

   Clementine 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 Clementine.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "remoteclient.h"

#include <QDataStream>
#include <QSettings>

#include "core/logging.h"
#include "networkremote.h"

RemoteClient::RemoteClient(Application* app, QTcpSocket* client)
    : app_(app),
      downloader_(false),
      client_(client),
      song_sender_(new SongSender(app, this)) {
  reading_protobuf_ = false;

  // Connect to the slot IncomingData when receiving data
  connect(client, SIGNAL(readyRead()), this, SLOT(IncomingData()));

  // Check if we use auth code
  QSettings s;

  s.beginGroup(NetworkRemote::kSettingsGroup);
  use_auth_code_ = s.value("use_auth_code", false).toBool();
  auth_code_ = s.value("auth_code", 0).toInt();
  allow_downloads_ = s.value("allow_downloads", false).toBool();
  files_root_folder_ = s.value("files_root_folder", "").toString();
  files_music_extensions_ =
      s.value("files_music_extensions",
              Application::kDefaultMusicExtensionsAllowedRemotely)
          .toStringList();
  s.endGroup();

  // If we don't use an auth code, we don't need to authenticate the client.
  authenticated_ = !use_auth_code_;
}

RemoteClient::~RemoteClient() {
  client_->close();
  if (client_->state() == QAbstractSocket::ConnectedState)
    client_->waitForDisconnected(2000);

  song_sender_->deleteLater();
  client_->deleteLater();
}

void RemoteClient::setDownloader(bool downloader) { downloader_ = downloader; }

void RemoteClient::IncomingData() {
  while (client_->bytesAvailable()) {
    if (!reading_protobuf_) {
      // If we have less than 4 byte, we cannot read the length. Wait for more
      // data
      if (client_->bytesAvailable() < 4) {
        break;
      }
      // Read the length of the next message
      QDataStream s(client_);
      s >> expected_length_;

      // Receiving more than 128mb is very unlikely
      // Flush the data and disconnect the client
      if (expected_length_ > 134217728) {
        qLog(Debug) << "Received invalid data, disconnect client";
        qLog(Debug) << "expected_length_ =" << expected_length_;
        client_->close();
        return;
      }

      reading_protobuf_ = true;
    }

    // Read some of the message
    buffer_.append(
        client_->read(static_cast<qint32>(expected_length_) - buffer_.size()));

    // Did we get everything?
    if (buffer_.size() == static_cast<qint32>(expected_length_)) {
      // Parse the message
      ParseMessage(buffer_);

      // Clear the buffer
      buffer_.clear();
      reading_protobuf_ = false;
    }
  }
}

void RemoteClient::ParseMessage(const QByteArray& data) {
  cpb::remote::Message msg;
  if (!msg.ParseFromArray(data.constData(), data.size())) {
    qLog(Info) << "Couldn't parse data";
    return;
  }

  if (msg.type() == cpb::remote::CONNECT && use_auth_code_) {
    if (msg.request_connect().auth_code() != auth_code_) {
      DisconnectClient(cpb::remote::Wrong_Auth_Code);
      return;
    } else {
      authenticated_ = true;
    }
  }

  if (msg.type() == cpb::remote::CONNECT) {
    setDownloader(msg.request_connect().downloader());
    qDebug() << "Downloader" << downloader_;
  }

  // Check if downloads are allowed
  if (msg.type() == cpb::remote::DOWNLOAD_SONGS && !allow_downloads_) {
    DisconnectClient(cpb::remote::Download_Forbidden);
    return;
  }

  if (msg.type() == cpb::remote::DISCONNECT) {
    client_->abort();
    qDebug() << "Client disconnected";
    return;
  }

  // Check if the client has sent the correct auth code
  if (!authenticated_) {
    DisconnectClient(cpb::remote::Not_Authenticated);
    return;
  }

  // Now parse the other data
  emit Parse(msg);
}

void RemoteClient::DisconnectClient(cpb::remote::ReasonDisconnect reason) {
  cpb::remote::Message msg;
  msg.set_type(cpb::remote::DISCONNECT);

  msg.mutable_response_disconnect()->set_reason_disconnect(reason);
  SendDataToClient(&msg);

  // Just close the connection. The next time the outgoing data creator
  // sends a keep alive, the client will be deleted
  client_->close();
}

// Sends data to client without check if authenticated
void RemoteClient::SendDataToClient(cpb::remote::Message* msg) {
  // Set the default version
  msg->set_version(msg->default_instance().version());

  // Check if we are still connected
  if (client_->state() == QTcpSocket::ConnectedState) {
    // Serialize the message
    std::string data = msg->SerializeAsString();

    // write the length of the data first
    QDataStream s(client_);
    s << qint32(data.length());
    if (downloader_) {
      // Don't use QDataSteam for large files
      client_->write(data.data(), data.length());
    } else {
      s.writeRawData(data.data(), data.length());
    }

    // Do NOT flush data here! If the client is already disconnected, it
    // causes a SIGPIPE termination!!!
  } else {
    qDebug() << "Closed";
    client_->close();
  }
}

void RemoteClient::SendData(cpb::remote::Message* msg) {
  // Check if client is authenticated before sending the data
  if (authenticated_) {
    SendDataToClient(msg);
  }
}

QAbstractSocket::SocketState RemoteClient::State() { return client_->state(); }
