/*
 * This file is part of PowerDNS or dnsdist.
 * Copyright -- PowerDNS.COM B.V. and its contributors
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * In addition, for the avoidance of any doubt, permission is granted to
 * link this program with OpenSSL and to (re)distribute the binaries
 * produced as the result of such linking.
 *
 * 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.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <set>
#include <thread>
#include <boost/utility.hpp>

#include "packetcache.hh"
#include "utility.hh"
#include "communicator.hh"
#include "dnsbackend.hh"
#include "ueberbackend.hh"
#include "packethandler.hh"
#include "logger.hh"
#include "dns.hh"
#include "arguments.hh"
#include "packetcache.hh"
#include "threadname.hh"

// there can be MANY OF THESE
void CommunicatorClass::retrievalLoopThread()
{
  setThreadName("pdns/comm-retri");
  for (;;) {
    d_suck_sem.wait();
    SuckRequest sr;
    {
      auto data = d_data.lock();
      if (data->d_suckdomains.empty()) {
        continue;
      }

      auto firstItem = data->d_suckdomains.begin();

      sr = *firstItem;
      data->d_suckdomains.erase(firstItem);
      if (data->d_suckdomains.empty()) {
        data->d_sorthelper = 0;
      }
    }
    suck(sr.domain, sr.primary, sr.force);
  }
}

void CommunicatorClass::loadArgsIntoSet(const char* listname, set<string>& listset)
{
  vector<string> parts;
  stringtok(parts, ::arg()[listname], ", \t");
  for (const auto& part : parts) {
    try {
      ComboAddress caIp(part, 53);
      listset.insert(caIp.toStringWithPort());
    }
    catch (PDNSException& e) {
      SLOG(g_log << Logger::Error << "Unparseable IP in " << listname << ". Error: " << e.reason << endl,
           d_slog->error(Logr::Error, e.reason, "Unparseable IP in list", "list", Logging::Loggable(listname)));
      _exit(1);
    }
  }
}

void CommunicatorClass::go()
{
  try {
    PacketHandler::s_allowNotifyFrom.toMasks(::arg()["allow-notify-from"]);
  }
  catch (PDNSException& e) {
    SLOG(g_log << Logger::Error << "Unparseable IP in allow-notify-from. Error: " << e.reason << endl,
         d_slog->error(Logr::Error, e.reason, "Unparseable IP in allow-notify-from"));
    _exit(1);
  }

  std::thread mainT([this]() { mainloop(); });
  mainT.detach();

  for (int nthreads = 0; nthreads < ::arg().asNum("retrieval-threads", 1); ++nthreads) {
    std::thread retrieve([this]() { retrievalLoopThread(); });
    retrieve.detach();
  }

  d_preventSelfNotification = ::arg().mustDo("prevent-self-notification");

  auto delay = ::arg().asNum("delay-notifications");
  if (delay > 0) {
    d_delayNotifications = static_cast<time_t>(delay);
  }

  try {
    d_onlyNotify.toMasks(::arg()["only-notify"]);
  }
  catch (PDNSException& e) {
    SLOG(g_log << Logger::Error << "Unparseable IP in only-notify. Error: " << e.reason << endl,
         d_slog->error(Logr::Error, e.reason, "Unparseable IP in only-notify"));
    _exit(1);
  }

  loadArgsIntoSet("also-notify", d_alsoNotify);

  loadArgsIntoSet("forward-notify", PacketHandler::s_forwardNotify);
}

void CommunicatorClass::mainloop()
{
  try {
    setThreadName("pdns/comm-main");
    signal(SIGPIPE, SIG_IGN);
    SLOG(g_log << Logger::Warning << "Primary/secondary communicator launching" << endl,
         d_slog->info(Logr::Warning, "Primary/secondary communicator launching"));

    d_tickinterval = ::arg().asNum("xfr-cycle-interval");

    int rc;
    time_t next;
    PacketHandler P(d_slog);

    makeNotifySockets();

    for (;;) {
      secondaryRefresh(&P);
      primaryUpdateCheck(&P);
      doNotifications(&P); // this processes any notification acknowledgements and actually send out our own notifications

      next = time(nullptr) + d_tickinterval;

      while (time(nullptr) < next) {
        rc = d_any_sem.tryWait();

        if (rc != 0) {
          bool extraSecondaryRefresh = false;
          Utility::sleep(1);
          {
            auto data = d_data.lock();
            if (data->d_tocheck.size()) {
              extraSecondaryRefresh = true;
            }
          }
          if (extraSecondaryRefresh)
            secondaryRefresh(&P);
        }
        else {
          // eat up extra posts to avoid busy looping if many posts were done
          while (d_any_sem.tryWait() == 0) {
          }
          break; // something happened
        }
        // this gets executed about once per second
        doNotifications(&P);
      }
    }
  }
  catch (PDNSException& ae) {
    SLOG(g_log << Logger::Error << "Exiting because communicator thread died with error: " << ae.reason << endl,
         d_slog->error(Logr::Error, ae.reason, "Exiting because communicator thread died with error"));
    Utility::sleep(1);
    _exit(1);
  }
  catch (std::exception& e) {
    SLOG(g_log << Logger::Error << "Exiting because communicator thread died with STL error: " << e.what() << endl,
         d_slog->error(Logr::Error, e.what(), "Exiting because communicator thread died with STL error"));
    _exit(1);
  }
  catch (...) {
    SLOG(g_log << Logger::Error << "Exiting because communicator caught unknown exception." << endl,
         d_slog->info(Logr::Error, "Exiting because communicator thread caught unknown exception"));
    _exit(1);
  }
}
