/* <!-- copyright */
/*
 * aria2 - The high speed download utility
 *
 * Copyright (C) 2013 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 "aria2api.h"

#include <functional>

#include "Platform.h"
#include "Context.h"
#include "DownloadEngine.h"
#include "OptionParser.h"
#include "Option.h"
#include "DlAbortEx.h"
#include "fmt.h"
#include "OptionHandler.h"
#include "RequestGroupMan.h"
#include "RequestGroup.h"
#include "MultiUrlRequestInfo.h"
#include "prefs.h"
#include "download_helper.h"
#include "LogFactory.h"
#include "PieceStorage.h"
#include "DownloadContext.h"
#include "FileEntry.h"
#include "BitfieldMan.h"
#include "DownloadContext.h"
#include "RpcMethodImpl.h"
#include "console.h"
#include "KeepRunningCommand.h"
#include "A2STR.h"
#include "SingletonHolder.h"
#include "Notifier.h"
#include "ApiCallbackDownloadEventListener.h"
#ifdef ENABLE_BITTORRENT
# include "bittorrent_helper.h"
#endif // ENABLE_BITTORRENT

namespace aria2 {

Session::Session(const KeyVals& options)
  : context(std::make_shared<Context>(false, 0, nullptr, options))
{}

Session::~Session()
{}

SessionConfig::SessionConfig()
  : keepRunning(false),
    useSignalHandler(true),
    downloadEventCallback(nullptr),
    userData(nullptr)
{}

namespace {
Platform* platform = nullptr;
} // namespace

int libraryInit()
{
  global::initConsole(true);
  try {
    platform = new Platform();
  } catch(RecoverableException& e) {
    A2_LOG_ERROR_EX(EX_EXCEPTION_CAUGHT, e);
    return -1;
  }
  LogFactory::setConsoleOutput(false);
  return 0;
}

int libraryDeinit()
{
  delete platform;
  return 0;
}

Session* sessionNew(const KeyVals& options, const SessionConfig& config)
{
  int rv;
  std::unique_ptr<Session> session;
  try {
    session = make_unique<Session>(options);
  } catch(RecoverableException& e) {
    return nullptr;
  }
  if(session->context->reqinfo) {
    if(!config.useSignalHandler) {
      session->context->reqinfo->setUseSignalHandler(false);
    }
    rv = session->context->reqinfo->prepare();
    if(rv != 0) {
      return nullptr;
    }
    auto& e = session->context->reqinfo->getDownloadEngine();
    if(config.keepRunning) {
      e->getRequestGroupMan()->setKeepRunning(true);
      // Add command to make aria2 keep event polling
      e->addCommand(make_unique<KeepRunningCommand>(e->newCUID(), e.get()));
    }
    if(config.downloadEventCallback) {
      session->listener = make_unique<ApiCallbackDownloadEventListener>
        (session.get(), config.downloadEventCallback, config.userData);
      SingletonHolder<Notifier>::instance()
        ->addDownloadEventListener(session->listener.get());
    }
  } else {
    return nullptr;
  }
  return session.release();
}

int sessionFinal(Session* session)
{
  error_code::Value rv = session->context->reqinfo->getResult();
  delete session;
  return rv;
}

int run(Session* session, RUN_MODE mode)
{
  auto& e = session->context->reqinfo->getDownloadEngine();
  return e->run(mode == RUN_ONCE);
}

int shutdown(Session* session, bool force)
{
  auto& e = session->context->reqinfo->getDownloadEngine();
  if(force) {
    e->requestForceHalt();
  } else {
    e->requestHalt();
  }
  // Skip next polling timeout. This avoids 1 second delay when there
  // is no Command other than KeepRunningCommand in the queue.
  e->setNoWait(true);
  return 0;
}

std::string gidToHex(A2Gid gid)
{
  return GroupId::toHex(gid);
}

A2Gid hexToGid(const std::string& hex)
{
  A2Gid gid;
  if(GroupId::toNumericId(gid, hex.c_str()) == 0) {
    return gid;
  } else {
    return 0;
  }
}

bool isNull(A2Gid gid)
{
  return gid == 0;
}

namespace {
template<typename InputIterator, typename Pred>
void apiGatherOption
(InputIterator first, InputIterator last,
 Pred pred,
 Option* option,
 const std::shared_ptr<OptionParser>& optionParser)
{
  for(; first != last; ++first) {
    const std::string& optionName = (*first).first;
    PrefPtr pref = option::k2p(optionName);
    const OptionHandler* handler = optionParser->find(pref);
    if(!handler || !pred(handler)) {
      // Just ignore the unacceptable options in this context.
      continue;
    }
    handler->parse(*option, (*first).second);
  }
}
} // namespace

namespace {
void apiGatherRequestOption(Option* option, const KeyVals& options,
                            const std::shared_ptr<OptionParser>& optionParser)
{
  apiGatherOption(options.begin(), options.end(),
                  std::mem_fn(&OptionHandler::getInitialOption),
                  option, optionParser);
}
} // namespace

namespace {
void apiGatherChangeableOption(Option* option, const KeyVals& options,
                               const std::shared_ptr<OptionParser>& optionParser)
{
  apiGatherOption(options.begin(), options.end(),
                  std::mem_fn(&OptionHandler::getChangeOption),
                  option, optionParser);
}
} // namespace

namespace {
void apiGatherChangeableOptionForReserved
(Option* option, const KeyVals& options,
 const std::shared_ptr<OptionParser>& optionParser)
{
  apiGatherOption(options.begin(), options.end(),
                  std::mem_fn(&OptionHandler::getChangeOptionForReserved),
                  option, optionParser);
}
} // namespace

namespace {
void apiGatherChangeableGlobalOption
(Option* option, const KeyVals& options,
 const std::shared_ptr<OptionParser>& optionParser)
{
  apiGatherOption(options.begin(), options.end(),
                  std::mem_fn(&OptionHandler::getChangeGlobalOption),
                  option, optionParser);
}
} // namespace

namespace {
void addRequestGroup(const std::shared_ptr<RequestGroup>& group,
                     DownloadEngine* e,
                     int position)
{
  if(position >= 0) {
    e->getRequestGroupMan()->insertReservedGroup(position, group);
  } else {
    e->getRequestGroupMan()->addReservedGroup(group);
  }
}
} // namespace

int addUri(Session* session,
           A2Gid* gid,
           const std::vector<std::string>& uris,
           const KeyVals& options,
           int position)
{
  auto& e = session->context->reqinfo->getDownloadEngine();
  auto requestOption = std::make_shared<Option>(*e->getOption());
  try {
    apiGatherRequestOption(requestOption.get(), options,
                           OptionParser::getInstance());
  } catch(RecoverableException& e) {
    A2_LOG_INFO_EX(EX_EXCEPTION_CAUGHT, e);
    return -1;
  }
  std::vector<std::shared_ptr<RequestGroup> > result;
  createRequestGroupForUri(result, requestOption, uris,
                           /* ignoreForceSeq = */ true,
                           /* ignoreLocalPath = */ true);
  if(!result.empty()) {
    addRequestGroup(result.front(), e.get(), position);
    if(gid) {
      *gid = result.front()->getGID();
    }
  }
  return 0;
}

int addMetalink(Session* session,
                std::vector<A2Gid>* gids,
                const std::string& metalinkFile,
                const KeyVals& options,
                int position)
{
#ifdef ENABLE_METALINK
  auto& e = session->context->reqinfo->getDownloadEngine();
  auto requestOption = std::make_shared<Option>(*e->getOption());
  std::vector<std::shared_ptr<RequestGroup> > result;
  try {
    apiGatherRequestOption(requestOption.get(), options,
                           OptionParser::getInstance());
    requestOption->put(PREF_METALINK_FILE, metalinkFile);
    createRequestGroupForMetalink(result, requestOption);
  } catch(RecoverableException& e) {
    A2_LOG_INFO_EX(EX_EXCEPTION_CAUGHT, e);
    return -1;
  }
  if(!result.empty()) {
    if(position >= 0) {
      e->getRequestGroupMan()->insertReservedGroup(position, result);
    } else {
      e->getRequestGroupMan()->addReservedGroup(result);
    }
    if(gids) {
      for(std::vector<std::shared_ptr<RequestGroup> >::const_iterator i =
            result.begin(), eoi = result.end(); i != eoi; ++i) {
        (*gids).push_back((*i)->getGID());
      }
    }
  }
  return 0;
#else // !ENABLE_METALINK
  return -1;
#endif // !ENABLE_METALINK
}

int addTorrent(Session* session,
               A2Gid* gid,
               const std::string& torrentFile,
               const std::vector<std::string>& webSeedUris,
               const KeyVals& options,
               int position)
{
#ifdef ENABLE_BITTORRENT
  auto& e = session->context->reqinfo->getDownloadEngine();
  auto requestOption = std::make_shared<Option>(*e->getOption());
  std::vector<std::shared_ptr<RequestGroup> > result;
  try {
    apiGatherRequestOption(requestOption.get(), options,
                           OptionParser::getInstance());
    requestOption->put(PREF_TORRENT_FILE, torrentFile);
    createRequestGroupForBitTorrent(result, requestOption, webSeedUris,
                                    torrentFile);
  } catch(RecoverableException& e) {
    A2_LOG_INFO_EX(EX_EXCEPTION_CAUGHT, e);
    return -1;
  }
  if(!result.empty()) {
    addRequestGroup(result.front(), e.get(), position);
    if(gid) {
      *gid = result.front()->getGID();
    }
  }
  return 0;
#else // !ENABLE_BITTORRENT
  return -1;
#endif // !ENABLE_BITTORRENT
}

int addTorrent(Session* session,
               A2Gid* gid,
               const std::string& torrentFile,
               const KeyVals& options,
               int position)
{
  return addTorrent(session, gid, torrentFile, std::vector<std::string>(),
                    options, position);
}

int removeDownload(Session* session, A2Gid gid, bool force)
{
  auto& e = session->context->reqinfo->getDownloadEngine();
  std::shared_ptr<RequestGroup> group = e->getRequestGroupMan()->findGroup(gid);
  if(group) {
    if(group->getState() == RequestGroup::STATE_ACTIVE) {
      if(force) {
        group->setForceHaltRequested(true, RequestGroup::USER_REQUEST);
      } else {
        group->setHaltRequested(true, RequestGroup::USER_REQUEST);
      }
      e->setRefreshInterval(std::chrono::milliseconds(0));
    } else {
      if(group->isDependencyResolved()) {
        e->getRequestGroupMan()->removeReservedGroup(gid);
      } else {
        return -1;
      }
    }
  } else {
    return -1;
  }
  return 0;
}

int pauseDownload(Session* session, A2Gid gid, bool force)
{
  auto& e = session->context->reqinfo->getDownloadEngine();
  std::shared_ptr<RequestGroup> group = e->getRequestGroupMan()->findGroup(gid);
  if(group) {
    bool reserved = group->getState() == RequestGroup::STATE_WAITING;
    if(pauseRequestGroup(group, reserved, force)) {
      e->setRefreshInterval(std::chrono::milliseconds(0));
      return 0;
    }
  }
  return -1;
}

int unpauseDownload(Session* session, A2Gid gid)
{
  auto& e = session->context->reqinfo->getDownloadEngine();
  std::shared_ptr<RequestGroup> group = e->getRequestGroupMan()->findGroup(gid);
  if(!group ||
     group->getState() != RequestGroup::STATE_WAITING ||
     !group->isPauseRequested()) {
    return -1;
  } else {
    group->setPauseRequested(false);
    e->getRequestGroupMan()->requestQueueCheck();
  }
  return 0;
}

int changePosition(Session* session, A2Gid gid, int pos, OffsetMode how)
{
  auto& e = session->context->reqinfo->getDownloadEngine();
  try {
    return e->getRequestGroupMan()->changeReservedGroupPosition(gid, pos, how);
  } catch(RecoverableException& e) {
    A2_LOG_INFO_EX(EX_EXCEPTION_CAUGHT, e);
    return -1;
  }
}

int changeOption(Session* session, A2Gid gid, const KeyVals& options)
{
  auto& e = session->context->reqinfo->getDownloadEngine();
  std::shared_ptr<RequestGroup> group = e->getRequestGroupMan()->findGroup(gid);
  if(group) {
    Option option;
    try {
      if(group->getState() == RequestGroup::STATE_ACTIVE) {
        apiGatherChangeableOption(&option, options,
                                  OptionParser::getInstance());
      } else {
        apiGatherChangeableOptionForReserved(&option, options,
                                             OptionParser::getInstance());
      }
    } catch(RecoverableException& err) {
      A2_LOG_INFO_EX(EX_EXCEPTION_CAUGHT, err);
      return -1;
    }
    changeOption(group, option, e.get());
    return 0;
  } else {
    return -1;
  }
}

const std::string& getGlobalOption(Session* session, const std::string& name)
{
  auto& e = session->context->reqinfo->getDownloadEngine();
  PrefPtr pref = option::k2p(name);
  if(OptionParser::getInstance()->find(pref)) {
    return e->getOption()->get(pref);
  } else {
    return A2STR::NIL;
  }
}

KeyVals getGlobalOptions(Session* session)
{
  auto& e = session->context->reqinfo->getDownloadEngine();
  const std::shared_ptr<OptionParser>& optionParser = OptionParser::getInstance();
  const Option* option = e->getOption();
  KeyVals options;
  for(size_t i = 1, len = option::countOption(); i < len; ++i) {
    PrefPtr pref = option::i2p(i);
    if(option->defined(pref) && optionParser->find(pref)) {
      options.push_back(KeyVals::value_type(pref->k, option->get(pref)));
    }
  }
  return options;
}

int changeGlobalOption(Session* session, const KeyVals& options)
{
  auto& e = session->context->reqinfo->getDownloadEngine();
  Option option;
  try {
    apiGatherChangeableGlobalOption(&option, options,
                                    OptionParser::getInstance());
  } catch(RecoverableException& err) {
    A2_LOG_INFO_EX(EX_EXCEPTION_CAUGHT, err);
    return -1;
  }
  changeGlobalOption(option, e.get());
  return 0;
}

GlobalStat getGlobalStat(Session* session)
{
  auto& e = session->context->reqinfo->getDownloadEngine();
  auto& rgman = e->getRequestGroupMan();
  TransferStat ts = rgman->calculateStat();
  GlobalStat res;
  res.downloadSpeed = ts.downloadSpeed;
  res.uploadSpeed = ts.uploadSpeed;
  res.numActive = rgman->getRequestGroups().size();
  res.numWaiting = rgman->getReservedGroups().size();
  res.numStopped = rgman->getDownloadResults().size();
  return res;
}

std::vector<A2Gid> getActiveDownload(Session* session)
{
  auto& e = session->context->reqinfo->getDownloadEngine();
  const RequestGroupList& groups = e->getRequestGroupMan()->getRequestGroups();
  std::vector<A2Gid> res;
  for(const auto& group : groups) {
    res.push_back(group->getGID());
  }
  return res;
}

namespace {
template<typename OutputIterator, typename InputIterator>
void createUriEntry
(OutputIterator out,
 InputIterator first, InputIterator last,
 UriStatus status)
{
  for(; first != last; ++first) {
    UriData uriData;
    uriData.uri = *first;
    uriData.status = status;
    out++ = uriData;
  }
}
} // namespace

namespace {
template<typename OutputIterator>
void createUriEntry
(OutputIterator out, const std::shared_ptr<FileEntry>& file)
{
  createUriEntry(out,
                 file->getSpentUris().begin(),
                 file->getSpentUris().end(),
                 URI_USED);
  createUriEntry(out,
                 file->getRemainingUris().begin(),
                 file->getRemainingUris().end(),
                 URI_WAITING);
}
} // namespace

namespace {
FileData createFileData
(const std::shared_ptr<FileEntry>& fe, int index, const BitfieldMan* bf)
{
  FileData file;
  file.index = index;
  file.path = fe->getPath();
  file.length = fe->getLength();
  file.completedLength = bf->getOffsetCompletedLength
    (fe->getOffset(), fe->getLength());
  file.selected = fe->isRequested();
  createUriEntry(std::back_inserter(file.uris), fe);
  return file;
}
} // namespace

namespace {
template<typename OutputIterator, typename InputIterator>
void createFileEntry
(OutputIterator out,
 InputIterator first, InputIterator last,
 const BitfieldMan* bf)
{
  size_t index = 1;
  for(; first != last; ++first) {
    out++ = createFileData(*first, index++, bf);
  }
}
} // namespace

namespace {
template<typename OutputIterator, typename InputIterator>
void createFileEntry
(OutputIterator out,
 InputIterator first, InputIterator last,
 int64_t totalLength,
 int32_t pieceLength,
 const std::string& bitfield)
{
  BitfieldMan bf(pieceLength, totalLength);
  bf.setBitfield(reinterpret_cast<const unsigned char*>(bitfield.data()),
                 bitfield.size());
  createFileEntry(out, first, last, &bf);
}
} // namespace

namespace {
template<typename OutputIterator, typename InputIterator>
void createFileEntry
(OutputIterator out,
 InputIterator first, InputIterator last,
 int64_t totalLength,
 int32_t pieceLength,
 const std::shared_ptr<PieceStorage>& ps)
{
  BitfieldMan bf(pieceLength, totalLength);
  if(ps) {
    bf.setBitfield(ps->getBitfield(), ps->getBitfieldLength());
  }
  createFileEntry(out, first, last, &bf);
}
} // namespace

namespace {
template<typename OutputIterator>
void pushRequestOption
(OutputIterator out,
 const std::shared_ptr<Option>& option,
 const std::shared_ptr<OptionParser>& oparser)
{
  for(size_t i = 1, len = option::countOption(); i < len; ++i) {
    PrefPtr pref = option::i2p(i);
    const OptionHandler* h = oparser->find(pref);
    if(h && h->getInitialOption() && option->defined(pref)) {
      out++ = KeyVals::value_type(pref->k, option->get(pref));
    }
  }
}
} // namespace

namespace {
const std::string& getRequestOption(const std::shared_ptr<Option>& option,
                                    const std::string& name)
{
  PrefPtr pref = option::k2p(name);
  if(OptionParser::getInstance()->find(pref)) {
    return option->get(pref);
  } else {
    return A2STR::NIL;
  }
}
} // namespace

namespace {
KeyVals getRequestOptions(const std::shared_ptr<Option>& option)
{
  KeyVals res;
  pushRequestOption(std::back_inserter(res), option,
                    OptionParser::getInstance());
  return res;
}
} // namespace

namespace {
struct RequestGroupDH : public DownloadHandle {
  RequestGroupDH(const std::shared_ptr<RequestGroup>& group)
    : group(group),
      ts(group->calculateStat())
  {}
  virtual ~RequestGroupDH() {}
  virtual DownloadStatus getStatus() CXX11_OVERRIDE
  {
    if(group->getState() == RequestGroup::STATE_ACTIVE) {
      return DOWNLOAD_ACTIVE;
    } else {
      if(group->isPauseRequested()) {
        return DOWNLOAD_PAUSED;
      } else {
        return DOWNLOAD_WAITING;
      }
    }
  }
  virtual int64_t getTotalLength() CXX11_OVERRIDE
  {
    return group->getTotalLength();
  }
  virtual int64_t getCompletedLength() CXX11_OVERRIDE
  {
    return group->getCompletedLength();
  }
  virtual int64_t getUploadLength() CXX11_OVERRIDE
  {
    return ts.allTimeUploadLength;
  }
  virtual std::string getBitfield() CXX11_OVERRIDE
  {
    const std::shared_ptr<PieceStorage>& ps = group->getPieceStorage();
    if(ps) {
      return std::string(reinterpret_cast<const char*>(ps->getBitfield()),
                         ps->getBitfieldLength());
    } else {
      return "";
    }
  }
  virtual int getDownloadSpeed() CXX11_OVERRIDE
  {
    return ts.downloadSpeed;
  }
  virtual int getUploadSpeed() CXX11_OVERRIDE
  {
    return ts.uploadSpeed;
  }
  virtual const std::string& getInfoHash() CXX11_OVERRIDE
  {
#ifdef ENABLE_BITTORRENT
    if(group->getDownloadContext()->hasAttribute(CTX_ATTR_BT)) {
      return bittorrent::getTorrentAttrs(group->getDownloadContext())
        ->infoHash;
    }
#endif // ENABLE_BITTORRENT
    return A2STR::NIL;
  }
  virtual size_t getPieceLength() CXX11_OVERRIDE
  {
    const std::shared_ptr<DownloadContext>& dctx = group->getDownloadContext();
    return dctx->getPieceLength();
  }
  virtual int getNumPieces() CXX11_OVERRIDE
  {
    return group->getDownloadContext()->getNumPieces();
  }
  virtual int getConnections() CXX11_OVERRIDE
  {
    return group->getNumConnection();
  }
  virtual int getErrorCode() CXX11_OVERRIDE
  {
    return 0;
  }
  virtual const std::vector<A2Gid>& getFollowedBy() CXX11_OVERRIDE
  {
    return group->followedBy();
  }
  virtual A2Gid getBelongsTo() CXX11_OVERRIDE
  {
    return group->belongsTo();
  }
  virtual const std::string& getDir() CXX11_OVERRIDE
  {
    return group->getOption()->get(PREF_DIR);
  }
  virtual std::vector<FileData> getFiles() CXX11_OVERRIDE
  {
    std::vector<FileData> res;
    const std::shared_ptr<DownloadContext>& dctx = group->getDownloadContext();
    createFileEntry(std::back_inserter(res),
                    dctx->getFileEntries().begin(),
                    dctx->getFileEntries().end(),
                    dctx->getTotalLength(), dctx->getPieceLength(),
                    group->getPieceStorage());
    return res;
  }
  virtual int getNumFiles() CXX11_OVERRIDE
  {
    const std::shared_ptr<DownloadContext>& dctx = group->getDownloadContext();
    return dctx->getFileEntries().size();
  }
  virtual FileData getFile(int index) CXX11_OVERRIDE
  {
    const std::shared_ptr<DownloadContext>& dctx = group->getDownloadContext();
    BitfieldMan bf(dctx->getPieceLength(), dctx->getTotalLength());
    const std::shared_ptr<PieceStorage>& ps = group->getPieceStorage();
    if(ps) {
      bf.setBitfield(ps->getBitfield(), ps->getBitfieldLength());
    }
    return createFileData(dctx->getFileEntries()[index-1], index, &bf);
  }
  virtual BtMetaInfoData getBtMetaInfo() CXX11_OVERRIDE
  {
    BtMetaInfoData res;
#ifdef ENABLE_BITTORRENT
    if(group->getDownloadContext()->hasAttribute(CTX_ATTR_BT)) {
      auto torrentAttrs =
        bittorrent::getTorrentAttrs(group->getDownloadContext());
      res.announceList = torrentAttrs->announceList;
      res.comment = torrentAttrs->comment;
      res.creationDate = torrentAttrs->creationDate;
      res.mode = torrentAttrs->mode;
      if(!torrentAttrs->metadata.empty()) {
        res.name = torrentAttrs->name;
      }
    } else
#endif // ENABLE_BITTORRENT
      {
        res.creationDate = 0;
        res.mode = BT_FILE_MODE_NONE;
      }
    return res;
  }
  virtual const std::string& getOption(const std::string& name) CXX11_OVERRIDE
  {
    return getRequestOption(group->getOption(), name);
  }
  virtual KeyVals getOptions() CXX11_OVERRIDE
  {
    return getRequestOptions(group->getOption());
  }
  std::shared_ptr<RequestGroup> group;
  TransferStat ts;
};
} // namespace

namespace {
struct DownloadResultDH : public DownloadHandle {
  DownloadResultDH(std::shared_ptr<DownloadResult> dr)
    : dr(std::move(dr))
  {}
  virtual ~DownloadResultDH() {}
  virtual DownloadStatus getStatus() CXX11_OVERRIDE
  {
    switch(dr->result) {
    case error_code::FINISHED:
      return DOWNLOAD_COMPLETE;
    case error_code::REMOVED:
      return DOWNLOAD_REMOVED;
    default:
      return DOWNLOAD_ERROR;
    }
  }
  virtual int64_t getTotalLength() CXX11_OVERRIDE
  {
    return dr->totalLength;
  }
  virtual int64_t getCompletedLength() CXX11_OVERRIDE
  {
    return dr->completedLength;
  }
  virtual int64_t getUploadLength() CXX11_OVERRIDE
  {
    return dr->uploadLength;
  }
  virtual std::string getBitfield() CXX11_OVERRIDE
  {
    return dr->bitfield;
  }
  virtual int getDownloadSpeed() CXX11_OVERRIDE
  {
    return 0;
  }
  virtual int getUploadSpeed() CXX11_OVERRIDE
  {
    return 0;
  }
  virtual const std::string& getInfoHash() CXX11_OVERRIDE
  {
    return dr->infoHash;
  }
  virtual size_t getPieceLength() CXX11_OVERRIDE
  {
    return dr->pieceLength;
  }
  virtual int getNumPieces() CXX11_OVERRIDE
  {
    return dr->numPieces;
  }
  virtual int getConnections() CXX11_OVERRIDE
  {
    return 0;
  }
  virtual int getErrorCode() CXX11_OVERRIDE
  {
    return dr->result;
  }
  virtual const std::vector<A2Gid>& getFollowedBy() CXX11_OVERRIDE
  {
    return dr->followedBy;
  }
  virtual A2Gid getBelongsTo() CXX11_OVERRIDE
  {
    return dr->belongsTo;
  }
  virtual const std::string& getDir() CXX11_OVERRIDE
  {
    return dr->dir;
  }
  virtual std::vector<FileData> getFiles() CXX11_OVERRIDE
  {
    std::vector<FileData> res;
    createFileEntry(std::back_inserter(res),
                    dr->fileEntries.begin(), dr->fileEntries.end(),
                    dr->totalLength, dr->pieceLength, dr->bitfield);
    return res;
  }
  virtual int getNumFiles() CXX11_OVERRIDE
  {
    return dr->fileEntries.size();
  }
  virtual FileData getFile(int index) CXX11_OVERRIDE
  {
    BitfieldMan bf(dr->pieceLength, dr->totalLength);
    bf.setBitfield(reinterpret_cast<const unsigned char*>(dr->bitfield.data()),
                   dr->bitfield.size());
    return createFileData(dr->fileEntries[index-1], index, &bf);
  }
  virtual BtMetaInfoData getBtMetaInfo() CXX11_OVERRIDE
  {
    return BtMetaInfoData();
  }
  virtual const std::string& getOption(const std::string& name) CXX11_OVERRIDE
  {
    return getRequestOption(dr->option, name);
  }
  virtual KeyVals getOptions() CXX11_OVERRIDE
  {
    return getRequestOptions(dr->option);
  }
  std::shared_ptr<DownloadResult> dr;
};
} // namespace

DownloadHandle* getDownloadHandle(Session* session, A2Gid gid)
{
  auto& e = session->context->reqinfo->getDownloadEngine();
  auto& rgman = e->getRequestGroupMan();
  std::shared_ptr<RequestGroup> group = rgman->findGroup(gid);
  if(group) {
    return new RequestGroupDH(group);
  } else {
    std::shared_ptr<DownloadResult> ds = rgman->findDownloadResult(gid);
    if(ds) {
      return new DownloadResultDH(ds);
    }
  }
  return nullptr;
}

void deleteDownloadHandle(DownloadHandle* dh)
{
  delete dh;
}

} // namespace aria2
