/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "PoolStatsSampler.hpp"

#include <string>

#include "../AdminRegion.hpp"
#include "../CacheImpl.hpp"
#include "../ClientHealthStats.hpp"
#include "../ClientProxyMembershipID.hpp"
#include "../ThinClientPoolDM.hpp"
#include "GeodeStatisticsFactory.hpp"

namespace apache {
namespace geode {
namespace statistics {

using std::chrono::duration_cast;
using std::chrono::high_resolution_clock;
using std::chrono::milliseconds;
using std::chrono::nanoseconds;

PoolStatsSampler::PoolStatsSampler(milliseconds sampleRate, CacheImpl* cache,
                                   ThinClientPoolDM* distMan)
    : m_running(false),
      m_stopRequested(false),
      m_sampleRate(sampleRate),
      m_adminRegion(AdminRegion::create(cache, distMan)),
      m_distMan(distMan),
      m_statisticsFactory(
          cache->getStatisticsManager().getStatisticsFactory()) {}

void PoolStatsSampler::svc() {
  client::Log::setThreadName("NC PSS Thread");
  while (!m_stopRequested) {
    auto sampleStart = high_resolution_clock::now();
    putStatsInAdminRegion();
    nanoseconds spentWorking = high_resolution_clock::now() - sampleStart;
    auto sleepDuration =
        m_sampleRate - duration_cast<milliseconds>(spentWorking);
    static const auto wakeInterval = milliseconds(100);
    while (!m_stopRequested && sleepDuration > milliseconds::zero()) {
      std::this_thread::sleep_for(sleepDuration > wakeInterval ? wakeInterval
                                                               : sleepDuration);
      sleepDuration -= wakeInterval;
    }
  }
}

void PoolStatsSampler::start() {
  if (!m_running.exchange(true)) {
    m_thread = std::thread(&PoolStatsSampler::svc, this);
  }
}

void PoolStatsSampler::stop() {
  m_stopRequested = true;
  m_thread.join();
}

bool PoolStatsSampler::isRunning() { return m_running; }

void PoolStatsSampler::putStatsInAdminRegion() {
  // Get Values of gets, puts,misses,listCalls,numThread
  try {
    static std::string clientId = "";
    if (!m_adminRegion->isDestroyed()) {
      int puts = 0, gets = 0, misses = 0, numListeners = 0, numThreads = 0,
          creates = 0;
      int64_t cpuTime = 0;
      if (m_statisticsFactory) {
        if (const auto cacheStatType =
                m_statisticsFactory->findType("CachePerfStats")) {
          if (const auto cachePerfStats =
                  m_statisticsFactory->findFirstStatisticsByType(
                      cacheStatType)) {
            puts = cachePerfStats->getInt("puts");
            gets = cachePerfStats->getInt("gets");
            misses = cachePerfStats->getInt("misses");
            creates = cachePerfStats->getInt("creates");
            numListeners =
                cachePerfStats->getInt("cacheListenerCallsCompleted");
            puts += creates;
          }
        }
      }
      static auto numCPU = std::thread::hardware_concurrency();
      auto obj = client::ClientHealthStats::create(
          gets, puts, misses, numListeners, numThreads, cpuTime, numCPU);
      const auto memId = m_distMan->getMembershipId();
      clientId = memId->getClientId();
      auto keyPtr = client::CacheableString::create(clientId.c_str());
      m_adminRegion->put(keyPtr, obj);
    }
  } catch (const client::AllConnectionsInUseException&) {
    LOGDEBUG("All connection are in use, trying again.");
  } catch (const client::NotConnectedException& ex) {
    try {
      std::rethrow_if_nested(ex);
    } catch (const client::NoAvailableLocatorsException&) {
      LOGDEBUG("No locators available, trying again.");
    } catch (...) {
      LOGDEBUG("Not connected to geode, trying again.");
    }
  } catch (...) {
    LOGDEBUG("Exception occurred, trying again.");
  }
}
}  // namespace statistics
}  // namespace geode
}  // namespace apache
