/*
 * Bittorrent Client using Qt4 and libtorrent.
 * Copyright (C) 2006  Christophe Dumez
 *
 * 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 2
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * In addition, as a special exception, the copyright holders give permission to
 * link this program with the OpenSSL project's "OpenSSL" library (or with
 * modified versions of it that use the same license as the "OpenSSL" library),
 * and distribute the linked executables. You must obey the GNU General Public
 * License in all respects for all of the code used other than "OpenSSL".  If you
 * modify file(s), you may extend this exception to your version of the file(s),
 * but you are not obligated to do so. If you do not wish to do so, delete this
 * exception statement from your version.
 *
 * Contact : chris@qbittorrent.org
 */

#include <QHttpRequestHeader>
#include <QTcpSocket>

#include <libtorrent/bencode.hpp>
#include <libtorrent/entry.hpp>

#include "qtracker.h"
#include "preferences.h"

using namespace libtorrent;

QTracker::QTracker(QObject *parent) :
  QTcpServer(parent)
{
  Q_ASSERT(Preferences().isTrackerEnabled());
  connect(this, SIGNAL(newConnection()), this, SLOT(handlePeerConnection()));
}

QTracker::~QTracker() {
  if(isListening()) {
    qDebug("Shutting down the embedded tracker...");
    close();
  }
  // TODO: Store the torrent list
}

void QTracker::handlePeerConnection()
{
  QTcpSocket *socket;
  while((socket = nextPendingConnection()))
  {
    qDebug("QTracker: New peer connection");
    connect(socket, SIGNAL(readyRead()), SLOT(readRequest()));
  }
}

bool QTracker::start()
{
  const int listen_port = Preferences().getTrackerPort();
  //
  if(isListening()) {
    if(serverPort() == listen_port) {
      // Already listening on the right port, just return
      return true;
    }
    // Wrong port, closing the server
    close();
  }
  qDebug("Starting the embedded tracker...");
  // Listen on the predefined port
  return listen(QHostAddress::Any, listen_port);
}

void QTracker::readRequest()
{
  QTcpSocket *socket = static_cast<QTcpSocket*>(sender());
  QByteArray input = socket->readAll();
  //qDebug("QTracker: Raw request:\n%s", input.data());
  HttpRequestParser parser;
  parser.write(input);
  if(!parser.isValid()) {
    qDebug("QTracker: Invalid HTTP Request:\n %s", qPrintable(parser.toString()));
    respondInvalidRequest(socket, 100, "Invalid request type");
    return;
  }
  //qDebug("QTracker received the following request:\n%s", qPrintable(parser.toString()));
  // Request is correct, is it a GET request?
  if(parser.method() != "GET") {
    qDebug("QTracker: Unsupported HTTP request: %s", qPrintable(parser.method()));
    respondInvalidRequest(socket, 100, "Invalid request type");
    return;
  }
  if(!parser.url().startsWith("/announce", Qt::CaseInsensitive)) {
    qDebug("QTracker: Unrecognized path: %s", qPrintable(parser.url()));
    respondInvalidRequest(socket, 100, "Invalid request type");
    return;
  }
  // OK, this is a GET request
  respondToAnnounceRequest(socket, parser);
}

void QTracker::respondInvalidRequest(QTcpSocket *socket, int code, QString msg)
{
  QHttpResponseHeader response;
  response.setStatusLine(code, msg);
  socket->write(response.toString().toLocal8Bit());
  socket->disconnectFromHost();
}

void QTracker::respondToAnnounceRequest(QTcpSocket *socket, const HttpRequestParser& parser)
{
  TrackerAnnounceRequest annonce_req;
  // IP
  annonce_req.peer.ip = socket->peerAddress().toString();
  // 1. Get info_hash
  if(parser.get("info_hash").isNull()) {
    qDebug("QTracker: Missing info_hash");
    respondInvalidRequest(socket, 101, "Missing info_hash");
    return;
  }
  annonce_req.info_hash = parser.get("info_hash");
  // info_hash cannot be longer than 20 bytes
  /*if(annonce_req.info_hash.toAscii().length() > 20) {
    qDebug("QTracker: Info_hash is not 20 byte long: %s (%d)", qPrintable(annonce_req.info_hash), annonce_req.info_hash.toAscii().length());
    respondInvalidRequest(socket, 150, "Invalid infohash");
    return;
  }*/
  // 2. Get peer ID
  if(parser.get("peer_id").isNull()) {
    qDebug("QTracker: Missing peer_id");
    respondInvalidRequest(socket, 102, "Missing peer_id");
    return;
  }
  annonce_req.peer.peer_id = parser.get("peer_id");
  // peer_id cannot be longer than 20 bytes
  /*if(annonce_req.peer.peer_id.length() > 20) {
    qDebug("QTracker: peer_id is not 20 byte long: %s", qPrintable(annonce_req.peer.peer_id));
    respondInvalidRequest(socket, 151, "Invalid peerid");
    return;
  }*/
  // 3. Get port
  if(parser.get("port").isNull()) {
    qDebug("QTracker: Missing port");
    respondInvalidRequest(socket, 103, "Missing port");
    return;
  }
  bool ok = false;
  annonce_req.peer.port = parser.get("port").toInt(&ok);
  if(!ok || annonce_req.peer.port < 1 || annonce_req.peer.port > 65535) {
    qDebug("QTracker: Invalid port number (%d)", annonce_req.peer.port);
    respondInvalidRequest(socket, 103, "Missing port");
    return;
  }
  // 4.  Get event
  annonce_req.event = "";
  if(!parser.get("event").isNull()) {
    annonce_req.event = parser.get("event");
    qDebug("QTracker: event is %s", qPrintable(annonce_req.event));
  }
  // 5. Get numwant
  annonce_req.numwant = 50;
  if(!parser.get("numwant").isNull()) {
    int tmp = parser.get("numwant").toInt();
    if(tmp > 0) {
      qDebug("QTracker: numwant=%d", tmp);
      annonce_req.numwant = tmp;
    }
  }
  // 6. no_peer_id (extension)
  annonce_req.no_peer_id = false;
  if(parser.hasKey("no_peer_id")) {
    annonce_req.no_peer_id = true;
  }
  // 7. TODO: support "compact" extension
  // Done parsing, now let's reply
  if(m_torrents.contains(annonce_req.info_hash)) {
    if(annonce_req.event == "stopped") {
      qDebug("QTracker: Peer stopped downloading, deleting it from the list");
      m_torrents[annonce_req.info_hash].remove(annonce_req.peer.qhash());
      return;
    }
  } else {
    // Unknown torrent
    if(m_torrents.size() == MAX_TORRENTS) {
      // Reached max size, remove a random torrent
      m_torrents.erase(m_torrents.begin());
    }
  }
  // Register the user
  PeerList peers = m_torrents.value(annonce_req.info_hash);
  if(peers.size() == MAX_PEERS_PER_TORRENT) {
    // Too many peers, remove a random one
    peers.erase(peers.begin());
  }
  peers[annonce_req.peer.qhash()] = annonce_req.peer;
  m_torrents[annonce_req.info_hash] = peers;
  // Reply
  ReplyWithPeerList(socket, annonce_req);
}

void QTracker::ReplyWithPeerList(QTcpSocket *socket, const TrackerAnnounceRequest &annonce_req)
{
  // Prepare the entry for bencoding
  entry::dictionary_type reply_dict;
  reply_dict["interval"] = entry(ANNOUNCE_INTERVAL);
  QList<QPeer> peers = m_torrents.value(annonce_req.info_hash).values();
  entry::list_type peer_list;
  foreach(const QPeer & p, peers) {
    //if(p != annonce_req.peer)
      peer_list.push_back(p.toEntry(annonce_req.no_peer_id));
  }
  reply_dict["peers"] = entry(peer_list);
  entry reply_entry(reply_dict);
  // bencode
  std::vector<char> buf;
  bencode(std::back_inserter(buf), reply_entry);
  QByteArray reply(&buf[0], buf.size());
  qDebug("QTracker: reply with the following bencoded data:\n %s", reply.constData());
  // HTTP reply
  QHttpResponseHeader response;
  response.setStatusLine(200, "OK");
  socket->write(response.toString().toLocal8Bit() + reply);
  socket->disconnectFromHost();
}


