/*
  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 "cripts/Lulu.hpp"
#include "cripts/Preamble.hpp"

namespace cripts
{

std::unordered_map<cripts::string_view, const Records *> Records::_gRecords;

Records::Records(const cripts::string_view name)
{
  TSOverridableConfigKey key;
  TSRecordDataType       type;

  if (TSHttpTxnConfigFind(name.data(), name.size(), &key, &type) == TS_SUCCESS) {
    _name = name;
    _key  = key;
    _type = type;
  } else {
    CFatal("[Records]: Invalid configuration variable '%.*s'", static_cast<int>(name.size()), name.data());
  }
}

Records::ValueType
Records::_get(const cripts::Context *context) const
{
  TSAssert(context->state.txnp);

  switch (_type) {
  case TS_RECORDDATATYPE_INT: {
    TSMgmtInt i;

    if (TSHttpTxnConfigIntGet(context->state.txnp, _key, &i) == TS_SUCCESS) {
      return i;
    }
  } break;
  case TS_RECORDDATATYPE_FLOAT: {
    TSMgmtFloat f;

    if (TSHttpTxnConfigFloatGet(context->state.txnp, _key, &f) == TS_SUCCESS) {
      return f;
    }
  } break;
  case TS_RECORDDATATYPE_STRING: {
    return std::string{GetSV(context)};
  } break;
  default:
    CFatal("[Records]: Invalid configuration type");
    return 0;
  }

  return 0;
}

const cripts::string_view
Records::GetSV(const cripts::Context *context) const
{
  TSAssert(context->state.txnp);

  switch (_type) {
  case TS_RECORDDATATYPE_STRING: {
    const char *s   = nullptr;
    int         len = 0;

    if (TSHttpTxnConfigStringGet(context->state.txnp, _key, &s, &len) == TS_SUCCESS) {
      return {s, len};
    }
  } break;
  default:
    CFatal("[Records]: Invalid configuration type for GetSV()");
    break;
  }

  return {};
}

bool
Records::_set(const cripts::Context *context, const ValueType &value) const
{
  TSAssert(context->state.txnp);

  switch (_type) {
  case TS_RECORDDATATYPE_INT: {
    TSMgmtInt i = std::get<TSMgmtInt>(value);

    if (TSHttpTxnConfigIntSet(context->state.txnp, _key, i) != TS_SUCCESS) {
      TSError("Failed to set integer configuration '%s'", _name.c_str());
      return false;
    }
    CDebug("Set integer configuration '{}' to {}", _name.c_str(), i);
  } break;
  case TS_RECORDDATATYPE_FLOAT: {
    TSMgmtFloat f = std::get<TSMgmtFloat>(value);

    if (TSHttpTxnConfigFloatSet(context->state.txnp, _key, f) != TS_SUCCESS) {
      TSError("Failed to set float configuration '%s'", _name.c_str());
      return false;
    }
    CDebug("Set float configuration '{}' to {}", _name.c_str(), f);
  } break;
  case TS_RECORDDATATYPE_STRING: {
    auto &str = std::get<std::string>(value);

    SetSV(context, {str.data(), str.size()});
  } break;
  default:
    CFatal("[Records]: Invalid configuration type");
    return false;
  }

  return true; // Success
}

bool
Records::SetSV(const cripts::Context *context, const cripts::string_view value) const
{
  TSAssert(context->state.txnp);

  switch (_type) {
  case TS_RECORDDATATYPE_STRING: {
    if (TSHttpTxnConfigStringSet(context->state.txnp, _key, value.data(), value.size()) != TS_SUCCESS) {
      TSError("Failed to set string configuration '%s'", _name.c_str());
      return false;
    }
    CDebug("Set string configuration '{}' to '{}'", _name.c_str(), value);
  } break;
  default:
    CFatal("[Records]: Invalid configuration type for setSV()");
    return false;
  }

  return true; // Success
}

// Static members for the records "cache"
void
Records::Add(const Records *rec)
{
  CAssert(rec->Loaded());
  auto it = _gRecords.find(rec->Name());

  CAssert(it == _gRecords.end());
  _gRecords[rec->Name()] = rec;
}

const Records *
Records::Lookup(const cripts::string_view name)
{
  auto it = _gRecords.find(name);

  if (it != _gRecords.end()) {
    CAssert(it->second->Loaded());
    return it->second;
  }

  return nullptr;
}

} // namespace cripts
