/*
  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 <pthread.h>
#include <unistd.h>
#include <thread>

#include "cripts/Lulu.hpp"
#include "cripts/Preamble.hpp"

#include "tsutil/StringConvert.h"
#include "tscore/Random.h"

#if CRIPTS_HAS_MAXMIND
#include <maxminddb.h>

MMDB_s              *gMaxMindDB    = nullptr;
const cripts::string maxMindDBPath = CRIPTS_MAXMIND_DB;
#endif

void
global_initialization()
{
  std::srand(std::time(nullptr) * getpid());

#if CRIPTS_HAS_MAXMIND
  gMaxMindDB = new MMDB_s;

  int status = MMDB_open(maxMindDBPath.c_str(), MMDB_MODE_MMAP, gMaxMindDB);
  if (MMDB_SUCCESS != status) {
    TSError("[Cripts] Cannot open %s - %s", maxMindDBPath.c_str(), MMDB_strerror(status));
    delete gMaxMindDB;
    gMaxMindDB = nullptr;
    return;
  }
#endif

  // Initialize various sub modules
  cripts::Plugin::Remap::Initialize();
}

integer
integer_helper(std::string_view sv)
{
  integer res    = INT64_MIN;
  auto [ptr, ec] = std::from_chars(sv.data(), sv.data() + sv.size(), res);

  if (ec == std::errc()) {
    return res;
  } else {
    return INT64_MIN;
  }
}

int
cripts::Random(int max)
{
  return ts::Random::random() % max + 1; // [1..max]
}

namespace cripts::details
{

template <typename T>
std::vector<T>
Splitter(T input, char delim)
{
  std::vector<T> output;
  size_t         first = 0;

  while (first < input.size()) {
    const auto second = input.find_first_of(delim, first);

    if (first != second) {
      output.emplace_back(input.substr(first, second - first));
    }
    if (second == cripts::string_view::npos) {
      break;
    }
    first = second + 1;
  }

  return output; // RVO
}

} // namespace cripts::details

cripts::string
cripts::Hex(const cripts::string &str)
{
  return ts::hex(str);
}

cripts::string
cripts::Hex(cripts::string_view sv)
{
  return ts::hex(sv);
}

cripts::string
cripts::UnHex(const cripts::string &str)
{
  return ts::unhex(str);
}

cripts::string
cripts::UnHex(cripts::string_view sv)
{
  return ts::unhex(sv);
}

cripts::string::operator integer() const
{
  return swoc::svtoi(*this);
}

// This doesn't deal with upper/lower case
cripts::string::operator bool() const
{
  if (empty()) {
    return false;
  }

  if (size() == 1) {
    return *this != "0";
  }

  if (size() == 4) {
    return *this == "true";
  }

  if (size() == 2) {
    return (*this == "on" || *this == "no");
  }

  return false;
}

std::vector<cripts::string_view>
cripts::string::split(char delim) const &
{
  return details::Splitter<cripts::string_view>(*this, delim);
}

std::vector<cripts::string_view>
cripts::Splitter(cripts::string_view input, char delim)
{
  return details::Splitter<cripts::string_view>(input, delim);
}

bool
cripts::Control::Base::_get(cripts::Context *context) const
{
  return TSHttpTxnCntlGet(context->state.txnp, _ctrl);
}

void
cripts::Control::Base::_set(cripts::Context *context, bool value)
{
  TSHttpTxnCntlSet(context->state.txnp, _ctrl, value);
}

cripts::string_view
cripts::Versions::GetSV()
{
  if (_version.length() == 0) {
    const char *ver = TSTrafficServerVersionGet();

    _version = cripts::string_view(ver, strlen(ver)); // ToDo: Annoyingly, we have ambiquity on the operator=
  }

  return _version;
}

// Globals
cripts::Proxy    proxy;
cripts::Control  control;
cripts::Versions version;

std::string plugin_debug_tag;

// This is to allow us to have a global initialization routine
pthread_once_t init_once_control = PTHREAD_ONCE_INIT;
pthread_once_t debug_tag_control = PTHREAD_ONCE_INIT;
