// Copyright 2018 Open Source Robotics Foundation, Inc.
//
// Licensed 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.

#ifdef __cplusplus
extern "C"
{
#endif

#include "rcl/context.h"

#include <stdbool.h>

#include "./common.h"
#include "./context_impl.h"
#include "rcutils/stdatomic_helper.h"

rcl_context_t
rcl_get_zero_initialized_context(void)
{
  static rcl_context_t context = {
    .impl = NULL,
    .instance_id_storage = {0},
  };
  // this is not constexpr so it cannot be in the struct initialization
  context.global_arguments = rcl_get_zero_initialized_arguments();
  // ensure assumption about static storage
  static_assert(
    sizeof(context.instance_id_storage) >= sizeof(atomic_uint_least64_t),
    "expected rcl_context_t's instance id storage to be >= size of atomic_uint_least64_t");
  // initialize atomic
  atomic_init((atomic_uint_least64_t *)(&context.instance_id_storage), 0);
  return context;
}

// See `rcl_init()` for initialization of the context.

rcl_ret_t
rcl_context_fini(rcl_context_t * context)
{
  RCL_CHECK_ARGUMENT_FOR_NULL(context, RCL_RET_INVALID_ARGUMENT);
  if (!context->impl) {
    // Context is zero-initialized
    return RCL_RET_OK;
  }
  if (rcl_context_is_valid(context)) {
    RCL_SET_ERROR_MSG("rcl_shutdown() not called on the given context");
    return RCL_RET_INVALID_ARGUMENT;
  }
  RCL_CHECK_ALLOCATOR_WITH_MSG(
    &(context->impl->allocator), "invalid allocator", return RCL_RET_INVALID_ARGUMENT);
  return __cleanup_context(context);
}

// See `rcl_shutdown()` for invalidation of the context.

const rcl_init_options_t *
rcl_context_get_init_options(const rcl_context_t * context)
{
  RCL_CHECK_ARGUMENT_FOR_NULL(context, NULL);
  RCL_CHECK_FOR_NULL_WITH_MSG(context->impl, "context is zero-initialized", return NULL);
  return &(context->impl->init_options);
}

rcl_context_instance_id_t
rcl_context_get_instance_id(const rcl_context_t * context)
{
  RCL_CHECK_ARGUMENT_FOR_NULL(context, 0);
  return rcutils_atomic_load_uint64_t((atomic_uint_least64_t *)(&context->instance_id_storage));
}

rcl_ret_t
rcl_context_get_domain_id(rcl_context_t * context, size_t * domain_id)
{
  if (!rcl_context_is_valid(context)) {
    return RCL_RET_INVALID_ARGUMENT;
  }
  RCL_CHECK_ARGUMENT_FOR_NULL(domain_id, RCL_RET_INVALID_ARGUMENT);
  *domain_id = context->impl->rmw_context.actual_domain_id;
  return RCL_RET_OK;
}

bool
rcl_context_is_valid(const rcl_context_t * context)
{
  RCL_CHECK_ARGUMENT_FOR_NULL(context, false);
  return 0 != rcl_context_get_instance_id(context);
}

rmw_context_t *
rcl_context_get_rmw_context(rcl_context_t * context)
{
  RCL_CHECK_ARGUMENT_FOR_NULL(context, NULL);
  RCL_CHECK_FOR_NULL_WITH_MSG(context->impl, "context is zero-initialized", return NULL);
  return &(context->impl->rmw_context);
}

rcl_ret_t
__cleanup_context(rcl_context_t * context)
{
  rcl_ret_t ret = RCL_RET_OK;
  // reset the instance id to 0 to indicate "invalid" (should already be 0, but this is defensive)
  rcutils_atomic_store((atomic_uint_least64_t *)(&context->instance_id_storage), 0);

  // clean up global_arguments if initialized
  if (NULL != context->global_arguments.impl) {
    ret = rcl_arguments_fini(&(context->global_arguments));
    if (RCL_RET_OK != ret) {
      RCUTILS_SAFE_FWRITE_TO_STDERR(
        "[rcl|context.c:" RCUTILS_STRINGIFY(__LINE__)
        "] failed to finalize global arguments while cleaning up context, memory may be leaked: ");
      RCUTILS_SAFE_FWRITE_TO_STDERR(rcl_get_error_string().str);
      RCUTILS_SAFE_FWRITE_TO_STDERR("\n");
      rcl_reset_error();
    }
  }

  // if impl is null, nothing else can be cleaned up
  if (NULL != context->impl) {
    // pull allocator out for use during deallocation
    rcl_allocator_t allocator = context->impl->allocator;

    // finalize init options if valid
    if (NULL != context->impl->init_options.impl) {
      rcl_ret_t init_options_fini_ret = rcl_init_options_fini(&(context->impl->init_options));
      if (RCL_RET_OK != init_options_fini_ret) {
        if (RCL_RET_OK == ret) {
          ret = init_options_fini_ret;
        }
        RCUTILS_SAFE_FWRITE_TO_STDERR(
          "[rcl|context.c:" RCUTILS_STRINGIFY(__LINE__)
          "] failed to finalize init options while cleaning up context, memory may be leaked: ");
        RCUTILS_SAFE_FWRITE_TO_STDERR(rcl_get_error_string().str);
        RCUTILS_SAFE_FWRITE_TO_STDERR("\n");
        rcl_reset_error();
      }
    }

    // clean up rmw_context
    if (NULL != context->impl->rmw_context.implementation_identifier) {
      rmw_ret_t rmw_context_fini_ret = rmw_context_fini(&(context->impl->rmw_context));
      if (RMW_RET_OK != rmw_context_fini_ret) {
        if (RCL_RET_OK == ret) {
          ret = rcl_convert_rmw_ret_to_rcl_ret(rmw_context_fini_ret);
        }
        RCUTILS_SAFE_FWRITE_TO_STDERR(
          "[rcl|context.c:" RCUTILS_STRINGIFY(__LINE__)
          "] failed to finalize rmw context while cleaning up context, memory may be leaked: ");
        RCUTILS_SAFE_FWRITE_TO_STDERR(rcutils_get_error_string().str);
        RCUTILS_SAFE_FWRITE_TO_STDERR("\n");
        rcutils_reset_error();
      }
    }

    // clean up copy of argv if valid
    if (NULL != context->impl->argv) {
      int64_t i;
      for (i = 0; i < context->impl->argc; ++i) {
        if (NULL != context->impl->argv[i]) {
          allocator.deallocate(context->impl->argv[i], allocator.state);
        }
      }
      allocator.deallocate(context->impl->argv, allocator.state);
    }
    allocator.deallocate(context->impl, allocator.state);
  }  // if (NULL != context->impl)

  // zero-initialize the context
  *context = rcl_get_zero_initialized_context();

  return ret;
}

#ifdef __cplusplus
}
#endif
