/* <!-- copyright */
/*
 * aria2 - The high speed download utility
 *
 * Copyright (C) 2006 Tatsuhiro Tsujikawa
 *
 * 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 the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations
 * including the two.
 * 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) with this exception, 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.  If you delete this exception statement from all source
 * files in the program, then also delete it here.
 */
/* copyright --> */
#include "DefaultBtAnnounce.h"
#include "BtRegistry.h"
#include "LogFactory.h"
#include "Logger.h"
#include "MetaFileUtil.h"
#include "Dictionary.h"
#include "List.h"
#include "Data.h"
#include "DelegatingPeerListProcessor.h"
#include "Util.h"
#include "prefs.h"
#include "DlAbortEx.h"
#include "message.h"
#include "SimpleRandomizer.h"
#include "BtContext.h"
#include "PieceStorage.h"
#include "BtRuntime.h"
#include "PeerStorage.h"
#include "Peer.h"
#include "Option.h"
#include "StringFormat.h"
#include "A2STR.h"

namespace aria2 {

DefaultBtAnnounce::DefaultBtAnnounce(const BtContextHandle& btContext,
				     const Option* option):
  btContext(btContext),
  trackers(0),
  interval(DEFAULT_ANNOUNCE_INTERVAL),
  minInterval(DEFAULT_ANNOUNCE_INTERVAL),
  complete(0),
  incomplete(0),
  announceList(btContext->getAnnounceTiers()),
  option(option),
  logger(LogFactory::getInstance()),
  _randomizer(SimpleRandomizer::getInstance()),
  btRuntime(BT_RUNTIME(btContext)),
  pieceStorage(PIECE_STORAGE(btContext)),
  peerStorage(PEER_STORAGE(btContext))
{
  prevAnnounceTime.setTimeInSec(0);
  generateKey();
}

DefaultBtAnnounce::~DefaultBtAnnounce() {
}

void DefaultBtAnnounce::generateKey()
{
  key = Util::randomAlpha(8, _randomizer);
}

bool DefaultBtAnnounce::isDefaultAnnounceReady() {
  return (trackers == 0 && prevAnnounceTime.elapsed(minInterval) &&
	  !announceList.allTiersFailed());
}

bool DefaultBtAnnounce::isStoppedAnnounceReady() {
  return (trackers == 0 &&
	  btRuntime->isHalt() &&
	  announceList.countStoppedAllowedTier());
}

bool DefaultBtAnnounce::isCompletedAnnounceReady() {
  return (trackers == 0 &&
	  pieceStorage->allDownloadFinished() &&
	  announceList.countCompletedAllowedTier());
}

bool DefaultBtAnnounce::isAnnounceReady() {
  return
    isStoppedAnnounceReady() ||
    isCompletedAnnounceReady() ||
    isDefaultAnnounceReady();
}

std::string DefaultBtAnnounce::getAnnounceUrl() {
  if(isStoppedAnnounceReady()) {
    if(!announceList.currentTierAcceptsStoppedEvent()) {
      announceList.moveToStoppedAllowedTier();
    }
    announceList.setEvent(AnnounceTier::STOPPED);
  } else if(isCompletedAnnounceReady()) {
    if(!announceList.currentTierAcceptsCompletedEvent()) {
      announceList.moveToCompletedAllowedTier();
    }
    announceList.setEvent(AnnounceTier::COMPLETED);
  } else if(isDefaultAnnounceReady()) {
    // If download completed before "started" event is sent to a tracker,
    // we change the event to something else to prevent us from
    // sending "completed" event.
    if(pieceStorage->allDownloadFinished() &&
       announceList.getEvent() == AnnounceTier::STARTED) {
      announceList.setEvent(AnnounceTier::STARTED_AFTER_COMPLETION);
    }
  } else {
    return A2STR::NIL;
  }
  unsigned int numWant = 50;
  if(!btRuntime->lessThanMinPeers() || btRuntime->isHalt()) {
    numWant = 0;
  }
  TransferStat stat = peerStorage->calculateStat();
  uint64_t left = pieceStorage->getTotalLength()-pieceStorage->getCompletedLength();
  if(left < 0) {
    left = 0;
  }
  std::string url = announceList.getAnnounce()+
    "?info_hash="+Util::torrentUrlencode(btContext->getInfoHash(),
					btContext->getInfoHashLength())+
    "&peer_id="+Util::torrentUrlencode(btContext->getPeerId(), 20)+
    "&uploaded="+Util::uitos(stat.getSessionUploadLength())+
    "&downloaded="+Util::uitos(stat.getSessionDownloadLength())+
    "&left="+Util::uitos(left)+
    "&compact=1"+
    "&key="+key+
    "&numwant="+Util::uitos(numWant)+
    "&no_peer_id=1";
  if(btRuntime->getListenPort() > 0) {
    url += "&port="+Util::uitos(btRuntime->getListenPort());
  }
  std::string event = announceList.getEventString();
  if(!event.empty()) {
    url += "&event="+event;
  }
  if(!trackerId.empty()) {
    url += "&trackerid="+Util::torrentUrlencode(trackerId);
  }
  if(option->getAsBool(PREF_BT_REQUIRE_CRYPTO)) {
    url += "&requirecrypto=1";
  } else {
    url += "&supportcrypto=1";
  }
  return url;
}

void DefaultBtAnnounce::announceStart() {
  trackers++;
}

void DefaultBtAnnounce::announceSuccess() {
  trackers = 0;
  announceList.announceSuccess();
}

void DefaultBtAnnounce::announceFailure() {
  trackers = 0;
  announceList.announceFailure();
}

bool DefaultBtAnnounce::isAllAnnounceFailed() {
  return announceList.allTiersFailed();
}

void DefaultBtAnnounce::resetAnnounce() {
  prevAnnounceTime.reset();
  announceList.resetTier();
}

void
DefaultBtAnnounce::processAnnounceResponse(const unsigned char* trackerResponse,
					   size_t trackerResponseLength)
{
  SharedHandle<MetaEntry> entry(MetaFileUtil::bdecoding(trackerResponse,
							trackerResponseLength));
  const Dictionary* response = dynamic_cast<const Dictionary*>(entry.get());
  if(!response) {
    throw DlAbortEx(MSG_NULL_TRACKER_RESPONSE);
  }
  const Data* failureReasonData =
    dynamic_cast<const Data*>(response->get(BtAnnounce::FAILURE_REASON));
  if(failureReasonData) {
    throw DlAbortEx
      (StringFormat(EX_TRACKER_FAILURE,
		    failureReasonData->toString().c_str()).str());
  }
  const Data* warningMessageData =
    dynamic_cast<const Data*>(response->get(BtAnnounce::WARNING_MESSAGE));
  if(warningMessageData) {
    logger->warn(MSG_TRACKER_WARNING_MESSAGE,
		 warningMessageData->toString().c_str());
  }
  const Data* trackerIdData =
    dynamic_cast<const Data*>(response->get(BtAnnounce::TRACKER_ID));
  if(trackerIdData) {
    trackerId = trackerIdData->toString();
    logger->debug("Tracker ID:%s", trackerId.c_str());
  }
  const Data* intervalData =
    dynamic_cast<const Data*>(response->get(BtAnnounce::INTERVAL));
  if(intervalData) {
    time_t t = intervalData->toInt();
    if(t > 0) {
      interval = intervalData->toInt();
      logger->debug("Interval:%d", interval);
    }
  }
  const Data* minIntervalData =
    dynamic_cast<const Data*>(response->get(BtAnnounce::MIN_INTERVAL));
  if(minIntervalData) {
    time_t t = minIntervalData->toInt();
    if(t > 0) {
      minInterval = minIntervalData->toInt();
      logger->debug("Min interval:%d", minInterval);
    }
    if(minInterval > interval) {
      minInterval = interval;
    }
  } else {
    // Use interval as a minInterval if minInterval is not supplied.
    minInterval = interval;
  }
  const Data* completeData =
    dynamic_cast<const Data*>(response->get(BtAnnounce::COMPLETE));
  if(completeData) {
    complete = completeData->toInt();
    logger->debug("Complete:%d", complete);
  }
  const Data* incompleteData =
    dynamic_cast<const Data*>(response->get(BtAnnounce::INCOMPLETE));
  if(incompleteData) {
    incomplete = incompleteData->toInt();
    logger->debug("Incomplete:%d", incomplete);
  }
  const MetaEntry* peersEntry = response->get(BtAnnounce::PEERS);
  if(peersEntry &&
     !btRuntime->isHalt() &&
     btRuntime->lessThanMinPeers()) {
    DelegatingPeerListProcessor proc;
    std::deque<SharedHandle<Peer> > peers;
    proc.extractPeer(peers, peersEntry);
    peerStorage->addPeer(peers);
  }
  if(!peersEntry) {
    logger->info(MSG_NO_PEER_LIST_RECEIVED);
  }
}

bool DefaultBtAnnounce::noMoreAnnounce() {
  return (trackers == 0 &&
	  btRuntime->isHalt() &&
	  !announceList.countStoppedAllowedTier());
}

void DefaultBtAnnounce::shuffleAnnounce() {
  announceList.shuffle();
}

void DefaultBtAnnounce::setRandomizer(const RandomizerHandle& randomizer)
{
  _randomizer = randomizer;
}

void DefaultBtAnnounce::setBtRuntime(const BtRuntimeHandle& btRuntime)
{
  this->btRuntime = btRuntime;
}

BtRuntimeHandle DefaultBtAnnounce::getBtRuntime() const
{
  return btRuntime;
}

void DefaultBtAnnounce::setPieceStorage(const PieceStorageHandle& pieceStorage)
{
  this->pieceStorage = pieceStorage;
}

PieceStorageHandle DefaultBtAnnounce::getPieceStorage() const
{
  return pieceStorage;
}

void DefaultBtAnnounce::setPeerStorage(const PeerStorageHandle& peerStorage)
{
  this->peerStorage = peerStorage;
}

PeerStorageHandle DefaultBtAnnounce::getPeerStorage() const
{
  return peerStorage;
}

} // namespace aria2
