/*
  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 "ats_wasm.h"

#include <string>

namespace ats_wasm
{
// extended Wasm VM Integration object
proxy_wasm::LogLevel
ATSWasmVmIntegration::getLogLevel()
{
  if (dbg_ctl.on()) {
    return proxy_wasm::LogLevel::debug;
  } else {
    return proxy_wasm::LogLevel::error;
  }
}

void
ATSWasmVmIntegration::error(std::string_view message)
{
  TSError("%.*s", static_cast<int>(message.size()), message.data());
}

void
ATSWasmVmIntegration::trace(std::string_view message)
{
  Dbg(dbg_ctl, "%.*s", static_cast<int>(message.size()), message.data());
}

bool
ATSWasmVmIntegration::getNullVmFunction(std::string_view /* function_name ATS_UNUSED */, bool /* returns_word ATS_UNUSED */,
                                        int /* number_of_arguments ATS_UNUSED */, proxy_wasm::NullPlugin * /* plugin ATS_UNUSED */,
                                        void * /* ptr_to_function_return ATS_UNUSED */)
{
  return false;
}

// extended constructors to initialize mutex
Wasm::Wasm(const std::shared_ptr<WasmHandleBase> &base_wasm_handle, const WasmVmFactory &factory)
  : WasmBase(base_wasm_handle, factory)
{
  mutex_ = TSMutexCreate();
}

Wasm::Wasm(std::unique_ptr<WasmVm> wasm_vm, std::string_view vm_id, std::string_view vm_configuration, std::string_view vm_key,
           std::unordered_map<std::string, std::string> envs, AllowedCapabilitiesMap allowed_capabilities)
  : WasmBase(std::move(wasm_vm), vm_id, vm_configuration, vm_key, std::move(envs), std::move(allowed_capabilities))
{
  mutex_ = TSMutexCreate();
}

// function to retrieve mutex
TSMutex
Wasm::mutex() const
{
  return mutex_;
}

// functions to create contexts
ContextBase *
Wasm::createVmContext()
{
  return new Context(this);
}

ContextBase *
Wasm::createRootContext(const std::shared_ptr<PluginBase> &plugin)
{
  Dbg(dbg_ctl, "Create root context for ats plugin");
  return new Context(this, plugin);
}

ContextBase *
Wasm::createContext(const std::shared_ptr<PluginBase> &plugin)
{
  return new Context(this, plugin);
}

// Function to start a new root context
Context *
Wasm::start(const std::shared_ptr<PluginBase> &plugin, TSCont contp)
{
  auto it = root_contexts_.find(plugin->key());
  if (it != root_contexts_.end()) {
    auto *c = static_cast<Context *>(it->second.get());
    if (c->scheduler_cont() == nullptr) {
      c->initialize(contp);
    } else {
      // keep the old continuation
      TSContDestroy(contp);
    }
    c->onStart(plugin);
    return c;
  }
  auto  context     = std::unique_ptr<Context>(static_cast<Context *>(createRootContext(plugin)));
  auto *context_ptr = context.get();
  context_ptr->initialize(contp);
  root_contexts_[plugin->key()] = std::move(context);
  if (!context_ptr->onStart(plugin)) {
    TSContDestroy(contp);
    return nullptr;
  }
  return context_ptr;
}

// functions to manage lifecycle of VM
bool
Wasm::readyShutdown()
{
  // if there is a non-root context, there is an unfinished transaction
  for (const auto &n : contexts_) {
    if (!n.second->isRootContext()) {
      return false;
    }
  }

  // if there is an entry in timer_period_, there is a continuation still running for that root context
  return timer_period_.empty();
}

bool
Wasm::readyDelete()
{
  return (root_contexts_.empty() && pending_done_.empty() && pending_delete_.empty());
}

// functions to manage timer
bool
Wasm::existsTimerPeriod(uint32_t root_context_id)
{
  return timer_period_.find(root_context_id) != timer_period_.end();
}

std::chrono::milliseconds
Wasm::getTimerPeriod(uint32_t root_context_id)
{
  return timer_period_[root_context_id];
}

void
Wasm::removeTimerPeriod(uint32_t root_context_id)
{
  auto it = timer_period_.find(root_context_id);
  if (it != timer_period_.end()) {
    timer_period_.erase(it);
  }
}

// function to override error report
void
Wasm::error(std::string_view message)
{
  TSError("%.*s", static_cast<int>(message.size()), message.data());
}

} // namespace ats_wasm
