/* Copyright (C) 2019 MariaDB Corporation

   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; version 2 of
   the License.

   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. */

#pragma once

#include "ThreadPool.h"
#include "CloudStorage.h"
#include "SMLogging.h"
#include "Config.h"

#include <unordered_set>
#include <vector>
#include <string>

#include <boost/shared_ptr.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition.hpp>
#include <boost/filesystem/path.hpp>

namespace storagemanager
{
class Downloader : public ConfigListener
{
 public:
  Downloader();
  ~Downloader() override;

  // caller owns the memory for the strings.
  // errors are reported through errnos
  void download(const std::vector<const std::string*>& keys, std::vector<int>* errnos,
                std::vector<size_t>* sizes, const boost::filesystem::path& prefix, boost::mutex* lock);
  bool inProgress(const std::string&);  // call this holding the cache's lock
  const boost::filesystem::path& getTmpPath() const;

  void printKPIs() const;

  void configListener() override;

 private:
  uint maxDownloads;
  // boost::filesystem::path downloadPath;
  boost::mutex lock;

  class DownloadListener
  {
   public:
    DownloadListener(uint* counter, boost::condition* condvar);
    void downloadFinished();

   private:
    uint* counter;
    boost::condition* cond;
  };

  /* Possible optimization.  Downloads used to use pointers to strings to avoid an extra copy.
     Out of paranoid during debugging, I made it copy the strings instead for a clearer lifecycle.
     However, it _should_ be safe to do.
  */
  struct Download : public ThreadPool::Job
  {
    Download(std::string source, boost::filesystem::path _dlPath, boost::mutex*, Downloader*);
    explicit Download(std::string source);
    ~Download() override;
    void operator()() override;
    boost::filesystem::path dlPath;
    const std::string key;
    int dl_errno;  // to propagate errors from the download job to the caller
    size_t size;
    boost::mutex* lock;
    bool finished, itRan;
    Downloader* dl;
    std::vector<DownloadListener*> listeners;
  };

  struct DLHasher
  {
    size_t operator()(const boost::shared_ptr<Download>& d) const;
  };

  struct DLEquals
  {
    bool operator()(const boost::shared_ptr<Download>& d1, const boost::shared_ptr<Download>& d2) const;
  };

  typedef std::unordered_set<boost::shared_ptr<Download>, DLHasher, DLEquals> Downloads_t;
  Downloads_t downloads;
  boost::filesystem::path tmpPath;

  ThreadPool workers;
  CloudStorage* storage;
  SMLogging* logger;

  // KPIs
  size_t bytesDownloaded;
};

}  // namespace storagemanager
