/* Copyright (c) 2023 Contrast Security, Inc.  See
 * https://www.contrastsecurity.com/enduser-terms-0317a for more details. */

#include "cs__scope.h"
#include "../cs__common/cs__common.h"
#include <ruby.h>
#include <stdlib.h>

/*-----------------------------------------
 |  Calls to Contrast modules and classes
 ------------------------------------------*/

VALUE contrast, agent, components;
VALUE scope_interface, scope_inst_methods;
VALUE scope_mod, scope_klass;

/*---------
 |  Arrays
 ----------*/
static char truthy_arr[3][5] = {"true", "True", "TRUE"};

/*----------
 |  Helpers
 -----------*/

/**
 * @brief get scope for ec or create new
 *
 * @note [Ruby definition] EXECUTION_CONTEXT[Fiber.current] ||=
 * Contrast::Agent::Scope.new
 *
 * @return VALUE [Contrast::Agent::Scope]
 */
VALUE get_ec() {
    VALUE ec, scope_inst, new_inst, keys, fiber;

    ec = rb_const_get(scope_mod, rb_intern(rb_const_ec));
    scope_inst = rb_hash_aref(ec, rb_fiber_current());
    keys = rb_const_get(scope_mod, rb_intern(rb_const_ec_keys));
    fiber = rb_fiber_current();

    if (RB_TYPE_P(scope_inst, T_NIL)) {
        new_inst = rb_new_c_scope();
        rb_ary_push(keys, fiber);
        rb_hash_aset(ec, fiber, new_inst);
        return new_inst;
    } else {
        return scope_inst;
    }
}

/**
 * @brief  create new Scope class instance
 *
 * @return VALUE [Contrast::Agent::Scope]
 */
VALUE rb_new_c_scope() {
    return rb_class_new_instance(0, 0, scope_klass);
}

/**
 * @brief Increases scope value by 1.
 *
 * @param scope int
 * @return int
 */
int scope_increase(int scope) {
    int inc = scope;
    inc = inc + 1;

    return INT2FIX(inc);
}

/**
 * @brief Decreases scope value by 1.
 *
 * @param scope int
 * @return int
 */
int scope_decrease(int scope) {
    int inc = scope;
    inc = inc - 1;

    return INT2FIX(inc);
}

/**
 * @brief returns Qfalse or Qtrue if is in defined scope
 *
 * @param scope int
 * @return VALUE
 */
VALUE is_in_scope(int scope) {
    if (scope > 0) {
        return Qtrue;
    } else if (scope <= 0) {
        return Qfalse;
    }
}

/**
 * @error: raise NoMethodError, "Scope '#{ name.inspect }' is not registered as
 * a scope."
 */
void rb_raise_scope_no_method_err(const VALUE method_scope_sym) {
    rb_raise(rb_eNoMethodError,
             "Scope ':%" PRIsVALUE "' is not registered as a scope.",
             rb_sym_to_s(method_scope_sym));
}

/*---------------------------------
 |  @class Contrast::Agent::Scope
 ----------------------------------*/

/**
 * @brief sets scope instance variables.
 *
 * @param self VALUE
 * @param args VALUE
 * @return VALUE self
 *
 * @note def initialize
 *        @contrast_scope = 0
 *        @deserialization_scope = 0
 *        @split_scope = 0
 *       end
 */
VALUE contrast_scope_klass_init(VALUE self, VALUE args) {
    rb_iv_set(self, rb_iv_cntr_scope, contrast_scope_application_update());
    rb_iv_set(self, rb_iv_dslr_scope, INT2FIX(0));
    rb_iv_set(self, rb_iv_split_scope, INT2FIX(0));

    return self;
}

/**
 * @brief This function will determine if the Application Scope flag is set.
 * If the value set is not valid the Contrast Scope with start with 0.
 * If the value is validated, then the Contrast Scope will be set to 1.
 * If the value is nil the Contrast Scope will be set as default => 0.
 *
 * To be valid app scope must be one of the truthy_arr: ["true", "True", "TRUE",
 * "1"]
 */
VALUE contrast_scope_application_update() {
    /**
     * ENV variables are null-terminated strings.
     *
     * getenv() returns a pointer to a string within the environment list.
     * The caller must take care not to modify this string,
     * since that would change the environment of the process.
     *
     * [ Do not touch, do not free. We don't control it! ]
     */
    char *eVar = getenv(c_const_ctr_agent_app_scope);
    VALUE app_scope = eVar;

    /* check to see if the ENV variable is set */
    if (RTEST(app_scope)) {
        /* Application Scope is set*/
        int i;
        for (i = 0; i < sizeof(truthy_arr) / sizeof(truthy_arr[0]); i++) {
            if (strcmp(eVar, truthy_arr[i]) == 0) {
                return INT2FIX(1);
            }
        }
        /* this covers all of the false values */
        return INT2FIX(0);
    } else {
        /* Application Scope is not set ( NULL )*/
        return INT2FIX(0);
    }
}

/**
 * @brief Check if we are in contrast scope.
 * @note  @method in_contrast_scope?
 * @param self VALUE
 * @param args VALUE
 * @return VALUE [Boolean] check if we are in contrast scope
 *                    if the scope is above 0 return true.
 *
 * @note def in_contrast_scope?
 *        @contrast_scope.positive?
 *       end
 */
VALUE in_cntr_scope(VALUE self, VALUE args) {
    return is_in_scope(FIX2INT(rb_iv_get(self, rb_iv_cntr_scope)));
}

/**
 * @brief Enters contrast scope.
 *
 * @param self VALUE
 * @param args VALUE
 * @return VALUE [Integer] contrast scope increased.
 *
 * @note def enter_contrast_scope!
 *        @contrast_scope += 1
 *       end
 */
VALUE enter_cntr_scope(VALUE self, VALUE args) {
    int scope = FIX2INT(rb_iv_get(self, rb_iv_cntr_scope));
    rb_iv_set(self, rb_iv_cntr_scope, scope_increase(scope));

    return rb_iv_get(self, rb_iv_cntr_scope);
}

/**
 * @brief Exits contrast scope.
 *
 * @param self VALUE
 * @param args VALUE
 * @return VALUE [Integer] contrast scope decreased.
 *
 * @note def exit_contrast_scope!
 *         @contrast_scope -= 1
 *       end
 */
VALUE exit_cntr_scope(VALUE self, VALUE args) {
    int scope = FIX2INT(rb_iv_get(self, rb_iv_cntr_scope));
    rb_iv_set(self, rb_iv_cntr_scope, scope_decrease(scope));

    return rb_iv_get(self, rb_iv_cntr_scope);
}

/**
 * @brief Check if we are in deserialization scope.
 *
 * @param self VALUE
 * @param args VALUE
 * @return VALUE [Boolean] check if we are in deserialization scope
 *                    if the scope is above 0 return true.
 *
 * @note  def in_deserialization_scope?
 *         @deserialization_scope.positive?
 *        end
 */
VALUE in_dslr_scope(VALUE self, VALUE args) {
    return is_in_scope(FIX2INT(rb_iv_get(self, rb_iv_dslr_scope)));
}

/**
 * @brief Enters deserialization scope.
 *
 * @param self VALUE
 * @param args VALUE
 * @return VALUE [Integer] deserialization scope increased.
 *
 * @note def enter_deserialization_scope!
 *         @deserialization_scope += 1
 *       end
 */
VALUE enter_dsrl_scope(VALUE self, VALUE args) {
    int scope = FIX2INT(rb_iv_get(self, rb_iv_dslr_scope));
    rb_iv_set(self, rb_iv_dslr_scope, scope_increase(scope));

    return rb_iv_get(self, rb_iv_dslr_scope);
}

/**
 * @brief Exits deserialization scope.
 *
 * @param self VALUE
 * @param args VALUE
 * @return VALUE [Integer] deserialization scope decreased.
 *
 * @note def enter_deserialization_scope!
 *        @deserialization_scope += 1
 *       end

 */
VALUE exit_dsrl_scope(VALUE self, VALUE args) {
    int scope = FIX2INT(rb_iv_get(self, rb_iv_dslr_scope));
    rb_iv_set(self, rb_iv_dslr_scope, scope_decrease(scope));

    return rb_iv_get(self, rb_iv_dslr_scope);
}

/**
 * @brief Check if we are in split scope.
 *
 * @param self VALUE
 * @param args VALUE
 * @return VALUE [Boolean] check if we are in split scope
 *                    if the scope is above 0 return true.
 *
 * @note  def in_split_scope?
 *          @split_scope.positive?
 *        end
 */
VALUE in_split_scope(VALUE self, VALUE args) {
    return is_in_scope(FIX2INT(rb_iv_get(self, rb_iv_split_scope)));
}

/**
 * @brief Enters split scope.
 *
 * @param self VALUE
 * @param args VALUE
 * @return VALUE [Integer] split scope increased.
 *
 * @note def enter_split_scope!
 *         @split_scope += 1
 *       end
 */
VALUE enter_split_scope(VALUE self, VALUE args) {
    int scope = FIX2INT(rb_iv_get(self, rb_iv_split_scope));
    rb_iv_set(self, rb_iv_split_scope, scope_increase(scope));

    return rb_iv_get(self, rb_iv_split_scope);
}

/**
 * @brief Exits split scope.
 *
 * @param self VALUE
 * @param args VALUE
 * @return VALUE [Integer] split scope decreased.
 *
 * @note def enter_split_scope!
 *         @split_scope -= 1
 *       end
 */
VALUE exit_split_scope(VALUE self, VALUE args) {
    int scope = FIX2INT(rb_iv_get(self, rb_iv_split_scope));
    rb_iv_set(self, rb_iv_split_scope, scope_decrease(scope));

    return rb_iv_get(self, rb_iv_split_scope);
}

/**
 * @brief Returns split scope current depth.
 *
 * @param self VALUE
 * @param args VALUE
 * @return VALUE [Integer]
 *
 * @note def split_scope_depth
 *        @split_scope
 *       end
 */
VALUE split_scope_depth(VALUE self, VALUE args) {
    return rb_iv_get(self, rb_iv_split_scope);
}

/**
 * Static methods to be used, the cases are defined by the usage from the above
 * methods if more methods are added - please extend the case statements as they
 * are no longed dynamic.
 */

/**
 * @brief Check if we are in specific scope.
 *
 * @param self VALUE
 * @param method_scope_sym VALUE [Symbol] scope symbol representing scope to
 * check.
 * @return VALUE [Boolean] check if we are in passed scope.
 * @error: NoMethodError
 *
 * @note def in_scope? name
 *         case name
 *         when :contrast
 *           in_contrast_scope?
 *         when :deserialization
 *           in_deserialization_scope?
 *         when :split
 *           in_split_scope?
 *         else
 *           raise NoMethodError, "Scope '#{ name.inspect }' is not registered
 * as a scope." end end
 */
VALUE scope_klass_in_scope(VALUE self, VALUE method_scope_sym) {
    VALUE in_scope = Qnil;

    if (method_scope_sym == rb_sym_contrast) {
        /* in_contrast_scope? */
        in_scope = in_cntr_scope(self, 0);
    } else if (method_scope_sym == rb_sym_deserialization) {
        /* in_deserialization_scope? */
        in_scope = in_dslr_scope(self, 0);
    } else if (method_scope_sym == rb_sym_split) {
        /* in_split_scope? */
        in_scope = in_split_scope(self, 0);
    } else {
        rb_raise_scope_no_method_err(method_scope_sym);
    }

    return in_scope;
}

/**
 * @brief Enters specific scope.
 *
 * @param self VALUE
 * @param method_scope_sym VALUE [Symbol] scope symbol representing scope to
 * enter.
 * @return VALUE [Boolean] entered scope value increased.
 *
 * @note def enter_scope! name
 *         case name
 *       when :contrast
 *         enter_contrast_scope!
 *       when :deserialization
 *         enter_deserialization_scope!
 *       when :split
 *         enter_split_scope!
 *       else
 *         raise NoMethodError, "Scope '#{ name.inspect }' is not registered as
 * a scope." end end
 */
VALUE scope_klass_enter_scope(VALUE self, VALUE method_scope_sym) {
    VALUE enter_scope = Qnil;

    if (method_scope_sym == rb_sym_contrast) {
        enter_scope = enter_cntr_scope(self, 0);
    } else if (method_scope_sym == rb_sym_deserialization) {
        enter_scope = enter_dsrl_scope(self, 0);
    } else if (method_scope_sym == rb_sym_split) {
        enter_scope = enter_split_scope(self, 0);
    } else {
        rb_raise_scope_no_method_err(method_scope_sym);
    }

    return enter_scope;
}

/**
 * @brief Exits specific scope.
 *
 * @param self VALUE
 * @param method_scope_sym VALUE [Symbol] scope symbol representing scope to
 * exit.
 * @return VALUE [Boolean] entered scope value decreased.
 *
 * @note
 * def exit_scope! name
 *   case name
 *    when :contrast
 *      exit_contrast_scope!
 *    when :deserialization
 *      exit_deserialization_scope!
 *    when :split
 *      exit_split_scope!
 *    else
 *      raise NoMethodError, "Scope '#{ name.inspect }' is not registered as a
 * scope." end end
 */
VALUE scope_klass_exit_scope(VALUE self, VALUE method_scope_sym) {
    VALUE exit_scope = Qnil;

    if (method_scope_sym == rb_sym_contrast) {
        exit_scope = exit_cntr_scope(self, 0);
    } else if (method_scope_sym == rb_sym_deserialization) {
        exit_scope = exit_dsrl_scope(self, 0);
    } else if (method_scope_sym == rb_sym_split) {
        exit_scope = exit_split_scope(self, 0);
    } else {
        rb_raise_scope_no_method_err(method_scope_sym);
    }

    return exit_scope;
}

/*-----------------------------------------
 |  @class Contrast::Components::Interface
 ------------------------------------------*/

/**
 * @brief init set new scope for current fiber
 *
 * @param self VALUE
 * @param args VALUE
 * @return VALUE [Hash<Fiber => Scope>] ec
 *
 * @note EXECUTION_CONTEXT[Fiber.current] = Contrast::Agent::Scope.new
 */
VALUE contrast_scope_interface_init(VALUE self, VALUE args) {
    VALUE ec = rb_const_get(scope_mod, rb_intern(rb_const_ec));
    rb_hash_aset(ec, rb_fiber_current(), rb_new_c_scope());

    return ec;
}

/**
 * @brief This returns the scope governing the current execution context. Use
 * this sparingly, preferring the instance & class methods to access and query
 * scope, rather than interacting with the scope object directly.
 *
 * Alternative to Monitor => mutex.synchronize
 * rb_mutex_new(void)
 * rb_mutex_synchronize (VALUE mutex, VALUE(*func)(VALUE arg), VALUE arg)
 * Mutex.synchronize do ... end goes here:
 *
 * @param self VALUE
 * @param args VALUE
 * @return VALUE
 *
 * @note def scope_for_current_ec
 *         MONITOR.synchronize do
 *           return EXECUTION_CONTEXT[Fiber.current] ||=
 * Contrast::Agent::Scope.new end end
 */
VALUE contrast_scope_for_current_ec(VALUE self, VALUE args) {
    /* synchronize */
    VALUE mutex = rb_const_get(scope_mod, rb_intern(rb_const_mon));

    return rb_mutex_synchronize(mutex, get_ec, 0);
}

/*--------------------------------------------------------
 |  @module Contrast::Components::Scope::InstanceMethods
 ---------------------------------------------------------*/

/**
 * @brief Iterates over the method policy's scopes and enters in each one.
 *
 * @param self VALUE
 * @param scopes_to_enter VALUE [Array<Symbol>] Scopes form
 * method_policy#scopes_to_enter for the scope current method policy
 * @return VALUE [Array<Symbol>] scopes_to_enter
 *
 * @note def enter_method_scope! scopes_to_enter
 *        scopes_to_enter.each do |scope|
 *         enter_scope!(scope)
 *       end
 *      end
 */
VALUE inst_methods_enter_method_scope(VALUE self, VALUE scopes_to_enter) {
    VALUE scopes_ary, scope;

    scopes_ary = rb_ary_dup(scopes_to_enter);
    scope = rb_ary_pop(scopes_ary);

    while (!RB_TYPE_P(scope, T_NIL)) {
        inst_methods_enter_scope(self, scope);
        scope = rb_ary_pop(scopes_ary);
    }

    return scopes_to_enter;
}

/**
 * @brief Iterates over the method policy's scopes and exits each one.
 *
 * @param self VALUE
 * @param scopes_to_exit VALUE [Array<Symbol>] Scopes form
 * method_policy#scopes_to_exit for the scope current method policy
 * @return VALUE [Array<Symbol>] scopes_to_exit
 *
 * @note def enter_method_scope! scopes_to_exit
 *         scopes_to_exit.each do |scope|
 *          enter_scope!(scope)
 *        end
 *      end
 */
VALUE inst_methods_exit_method_scope(VALUE self, VALUE scopes_to_exit) {
    VALUE scopes_ary, scope;

    scopes_ary = rb_ary_dup(scopes_to_exit);
    scope = rb_ary_pop(scopes_ary);

    while (!RB_TYPE_P(scope, T_NIL)) {
        inst_methods_exit_scope(self, scope);
        scope = rb_ary_pop(scopes_ary);
    }

    return scopes_to_exit;
}

/**
 * For the InstanceMethods we need to call all the scope methods from the
 * current ec context All methods bellow are with same names as
 * Contrast::Agent::Scope class with the difference that they act as forwarders:
 *    def in_contrast_scope?
 *      scope_for_current_ec.in_contrast_scope?
 *    end
 */

/**
 * @brief scope_for_current_ec.in_contrast_scope?
 *
 * @param self VALUE
 * @param args VALUE
 * @return VALUE
 */
VALUE inst_methods_in_cntr_scope(VALUE self, VALUE args) {
    return is_in_scope(FIX2INT(
        rb_iv_get(contrast_scope_for_current_ec(self, 0), rb_iv_cntr_scope)));
}

/**
 * @brief scope_for_current_ec.enter_contrast_scope!
 *
 * @param self VALUE
 * @param args VALUE
 * @return VALUE
 */
VALUE inst_methods_enter_cntr_scope(VALUE self, VALUE args) {
    int scope = FIX2INT(
        rb_iv_get(contrast_scope_for_current_ec(self, 0), rb_iv_cntr_scope));
    rb_iv_set(contrast_scope_for_current_ec(self, 0), rb_iv_cntr_scope,
              scope_increase(scope));

    return rb_iv_get(contrast_scope_for_current_ec(self, 0), rb_iv_cntr_scope);
}

/**
 * @brief scope_for_current_ec.exit_contrast_scope!
 *
 * @param self VALUE
 * @param args VALUE
 * @return VALUE
 */
VALUE inst_methods_exit_cntr_scope(VALUE self, VALUE args) {
    int scope = FIX2INT(
        rb_iv_get(contrast_scope_for_current_ec(self, 0), rb_iv_cntr_scope));
    rb_iv_set(contrast_scope_for_current_ec(self, 0), rb_iv_cntr_scope,
              scope_decrease(scope));

    return rb_iv_get(contrast_scope_for_current_ec(self, 0), rb_iv_cntr_scope);
}

/**
 * @brief scope_for_current_ec.in_split_scope?
 *
 * @param self VALUE
 * @param args VALUE
 * @return VALUE
 */
VALUE inst_methods_in_split_scope(VALUE self, VALUE args) {
    return is_in_scope(FIX2INT(
        rb_iv_get(contrast_scope_for_current_ec(self, 0), rb_iv_split_scope)));
}

/**
 * @brief scope_for_current_ec.enter_split_scope!
 *
 * @param self VALUE
 * @param args VALUE
 * @return VALUE
 */
VALUE inst_methods_enter_split_scope(VALUE self, VALUE args) {
    int scope = FIX2INT(
        rb_iv_get(contrast_scope_for_current_ec(self, 0), rb_iv_split_scope));

    return rb_iv_set(contrast_scope_for_current_ec(self, 0), rb_iv_split_scope,
                     scope_increase(scope));
}

/**
 * @brief scope_for_current_ec.exit_split_scope!
 *
 * @param self VALUE
 * @param args VALUE
 * @return VALUE
 */
VALUE inst_methods_exit_split_scope(VALUE self, VALUE args) {
    int scope = FIX2INT(
        rb_iv_get(contrast_scope_for_current_ec(self, 0), rb_iv_split_scope));
    rb_iv_set(contrast_scope_for_current_ec(self, 0), rb_iv_split_scope,
              scope_decrease(scope));

    return rb_iv_get(contrast_scope_for_current_ec(self, 0), rb_iv_split_scope);
}

/**
 * @brief Get Deserialization scope iv.
 *
 * @param self
 * @param args
 * @return VALUE
 */
VALUE inst_methods_split_scope_depth(VALUE self, VALUE args) {
    return rb_iv_get(contrast_scope_for_current_ec(self, 0), rb_iv_split_scope);
}

/**
 * @brief scope_for_current_ec.in_deserialization_scope?
 *
 * @param self VALUE
 * @param args VALUE
 * @return VALUE
 */
VALUE inst_methods_in_dsrl_scope(VALUE self, VALUE args) {
    return is_in_scope(FIX2INT(
        rb_iv_get(contrast_scope_for_current_ec(self, 0), rb_iv_dslr_scope)));
}

/**
 * @brief scope_for_current_ec.enter_deserialization_scope!
 *
 * @param self VALUE
 * @param args VALUE
 * @return VALUE
 */
VALUE inst_methods_enter_dsrl_scope(VALUE self, VALUE args) {
    int scope = FIX2INT(
        rb_iv_get(contrast_scope_for_current_ec(self, 0), rb_iv_dslr_scope));
    rb_iv_set(contrast_scope_for_current_ec(self, 0), rb_iv_dslr_scope,
              scope_increase(scope));

    return rb_iv_get(contrast_scope_for_current_ec(self, 0), rb_iv_dslr_scope);
}

/**
 * @brief scope_for_current_ec.exit_deserialization_scope!
 *
 * @param self VALUE
 * @param args VALUE
 * @return VALUE
 */
VALUE inst_methods_exit_dsrl_scope(VALUE self, VALUE args) {
    int scope = FIX2INT(
        rb_iv_get(contrast_scope_for_current_ec(self, 0), rb_iv_dslr_scope));
    rb_iv_set(contrast_scope_for_current_ec(self, 0), rb_iv_dslr_scope,
              scope_decrease(scope));

    return rb_iv_get(contrast_scope_for_current_ec(self, 0), rb_iv_dslr_scope);
}

/**
 * @brief Check to see if we are in scope by parameter symbol passed. Raises
 * error if symbol is not valid.
 *
 * @param self VALUE
 * @param method_scope_sym VALUE [Symbol]
 * @error: rb_raise_scope_no_method_err [NoMethodError]
 * @return VALUE
 */
VALUE inst_methods_in_scope(VALUE self, VALUE method_scope_sym) {
    if (method_scope_sym == rb_sym_contrast) {
        inst_methods_in_cntr_scope(self, 0);
    } else if (method_scope_sym == rb_sym_deserialization) {
        inst_methods_in_dsrl_scope(self, 0);
    } else if (method_scope_sym == rb_sym_split) {
        inst_methods_in_split_scope(self, 0);
    } else {
        rb_raise_scope_no_method_err(method_scope_sym);
    }
}

/**
 * @brief Enters scope by parameter symbol passed. Raises error if symbol is not
 * valid.
 *
 * @param self VALUE
 * @param method_scope_sym VALUE [Symbol]
 * @error: rb_raise_scope_no_method_err [NoMethodError]
 * @return VALUE
 */
VALUE inst_methods_enter_scope(VALUE self, VALUE method_scope_sym) {
    if (method_scope_sym == rb_sym_contrast) {
        inst_methods_enter_cntr_scope(self, 0);
    } else if (method_scope_sym == rb_sym_deserialization) {
        inst_methods_enter_dsrl_scope(self, 0);
    } else if (method_scope_sym == rb_sym_split) {
        inst_methods_enter_split_scope(self, 0);
    } else {
        rb_raise_scope_no_method_err(method_scope_sym);
    }
}

/**
 * @brief Exits scope by parameter symbol passed. Raises error if symbol is not
 * valid.
 *
 * @param self VALUE
 * @param method_scope_sym VALUE [Symbol]
 * @error: rb_raise_scope_no_method_err [NoMethodError]
 * @return VALUE
 */
VALUE inst_methods_exit_scope(VALUE self, VALUE method_scope_sym) {
    if (method_scope_sym == rb_sym_contrast) {
        inst_methods_exit_cntr_scope(self, 0);
    } else if (method_scope_sym == rb_sym_deserialization) {
        inst_methods_exit_dsrl_scope(self, 0);
    } else if (method_scope_sym == rb_sym_split) {
        inst_methods_exit_split_scope(self, 0);
    } else {
        rb_raise_scope_no_method_err(method_scope_sym);
    }
}

/**
 * @brief Sweeps and cleans any unused Execution Contexts.
 * TODO: RUBY-534, #sweep_dead_ecs compensates for a lack of weak tables. when
 * we can use WeakRef, we should investigate removing this call and instead use
 * the WeakRef for the Execution Context's Keys or using our Finalizers Hash for
 * Fibers
 *
 * @param self VALUE
 * @param args VALUE
 * @return VALUE
 */
VALUE scope_mod_sweep_dead_ecs(VALUE self, VALUE args) {
    VALUE mutex, ec, ec_keys, key, test;

    mutex = rb_const_get(scope_mod, rb_intern(rb_const_mon));
    ec = rb_const_get(scope_mod, rb_intern(rb_const_ec));
    ec_keys = rb_const_get(scope_mod, rb_intern(rb_const_ec_keys));

    /* Check if the key is dead (terminated fiber) and delete if true. */
    int i = 0;
    int size = rb_hash_size(ec_keys);

    for (i = 0; i < size; ++i) {
        key = rb_ary_entry(ec_keys, i);
        test = key;
        if (!RB_TYPE_P(key, T_NIL)) {
            if (!rb_fiber_alive_p(key)) {
                rb_hash_delete(ec, key);
            }
        }
    }

    return ec;
}

void Init_cs__scope() {
    /* ivs */
    rb_iv_cntr_scope = "@contrast_scope";
    rb_iv_dslr_scope = "@deserialization_scope";
    rb_iv_split_scope = "@split_scope";

    /* constants */
    rb_const_mon = "MONITOR";
    rb_const_ec = "EXECUTION_CONTEXT";
    rb_const_ec_keys = "EC_KEYS";
    c_const_ctr_agent_app_scope = "CONTRAST__AGENT__RUBY__APPLICATION_SCOPE";

    /* Symbols */
    rb_sym_scope_mod = rb_intern("Scope");
    rb_sym_contrast = ID2SYM(rb_intern("contrast"));
    rb_sym_deserialization = ID2SYM(rb_intern("deserialization"));
    rb_sym_split = ID2SYM(rb_intern("split"));

    /* method names */
    rb_method_name_init = "initialize";
    rb_method_name_in_scope = "in_scope?";
    rb_method_name_enter_scope = "enter_scope!";
    rb_method_name_exit_scope = "exit_scope!";
    rb_method_name_scope_for_current_ec = "scope_for_current_ec";
    rb_method_name_in_cntr_scope = "in_contrast_scope?";
    rb_method_name_enter_cntr_scope = "enter_contrast_scope!";
    rb_method_name_exit_cntr_scope = "exit_contrast_scope!";
    rb_method_name_in_dslr_scope = "in_deserialization_scope?";
    rb_method_name_enter_dslr_scope = "enter_deserialization_scope!";
    rb_method_name_exit_dslr_scope = "exit_deserialization_scope!";
    rb_method_name_in_split_scope = "in_split_scope?";
    rb_method_name_enter_split_scope = "enter_split_scope!";
    rb_method_name_exit_split_scope = "exit_split_scope!";
    rb_method_name_split_scope_depth = "split_scope_depth";

    /* Define the new scope modules and objects */
    contrast = rb_define_module("Contrast");
    /* contrast::Agent */
    agent = rb_define_module_under(contrast, "Agent");
    /* components => Contrast::Components */
    components = rb_define_module_under(contrast, "Components");
    /* scope_mod => Contrast::Components::Scope */
    scope_mod = rb_define_module_under(components, "Scope");
    /* scope_interface => Contrast::Components::Scope::Interface */
    scope_interface = rb_define_class_under(scope_mod, "Interface", rb_cObject);
    /* scope_inst_methods => Contrast::Components::Scope::InstanceMethods */
    scope_inst_methods = rb_define_module_under(scope_mod, "InstanceMethods");
    /* scope_klass => Contrast::Agent::Scope */
    scope_klass = rb_define_class_under(agent, "Scope", rb_cObject);

    /**
     * @class Contrast::Agent::Scope
     */

    /* Instance methods: */
    rb_define_method(scope_klass, rb_method_name_init,
                     contrast_scope_klass_init, 0);
    rb_define_method(scope_klass, rb_method_name_in_cntr_scope, in_cntr_scope,
                     0);
    rb_define_method(scope_klass, rb_method_name_enter_cntr_scope,
                     enter_cntr_scope, 0);
    rb_define_method(scope_klass, rb_method_name_exit_cntr_scope,
                     exit_cntr_scope, 0);
    rb_define_method(scope_klass, rb_method_name_in_dslr_scope, in_dslr_scope,
                     0);
    rb_define_method(scope_klass, rb_method_name_enter_dslr_scope,
                     enter_dsrl_scope, 0);
    rb_define_method(scope_klass, rb_method_name_exit_dslr_scope,
                     exit_dsrl_scope, 0);
    rb_define_method(scope_klass, rb_method_name_in_split_scope, in_split_scope,
                     0);
    rb_define_method(scope_klass, rb_method_name_enter_split_scope,
                     enter_split_scope, 0);
    rb_define_method(scope_klass, rb_method_name_exit_split_scope,
                     exit_split_scope, 0);
    rb_define_method(scope_klass, rb_method_name_split_scope_depth,
                     split_scope_depth, 0);
    rb_define_method(scope_klass, rb_method_name_in_scope, scope_klass_in_scope,
                     1);
    rb_define_method(scope_klass, rb_method_name_enter_scope,
                     scope_klass_enter_scope, 1);
    rb_define_method(scope_klass, rb_method_name_exit_scope,
                     scope_klass_exit_scope, 1);

    /**
     * @class Contrast::Components::Interface
     */

    /* Interface#initialize */
    rb_define_method(scope_interface, rb_method_name_init,
                     contrast_scope_interface_init, 0);
    /* Interface#scope_for_current_ec */
    rb_define_method(scope_interface, rb_method_name_scope_for_current_ec,
                     contrast_scope_for_current_ec, 0);

    /**
     * @module Contrast::Components::Scope
     */

    /* Constants */

    /* Contrast::Components::Scope::EC_KEYS */
    rb_const_set(scope_mod, rb_intern(rb_const_ec_keys), rb_ary_new());
    /* Contrast::Components::Scope::EXECUTION_CONTEXT => {} */
    rb_define_const(scope_mod, rb_const_ec, rb_hash_new());
    /* Contrast::Components::Scope::MONITOR => Mutex.new.freeze */
    rb_define_const(scope_mod, rb_const_mon, rb_mutex_new());

    /**
     * @module Contrast::Components::Scope::InstanceMethods
     */

    /* InstanceMethods#scope_for_current_ec */
    rb_define_method(scope_inst_methods, rb_method_name_scope_for_current_ec,
                     contrast_scope_for_current_ec, 0);
    /* Forwarders */
    rb_define_method(scope_inst_methods, rb_method_name_in_cntr_scope,
                     inst_methods_in_cntr_scope, 0);
    rb_define_method(scope_inst_methods, rb_method_name_enter_cntr_scope,
                     inst_methods_enter_cntr_scope, 0);
    rb_define_method(scope_inst_methods, rb_method_name_exit_cntr_scope,
                     inst_methods_exit_cntr_scope, 0);
    rb_define_method(scope_inst_methods, rb_method_name_in_dslr_scope,
                     inst_methods_in_dsrl_scope, 0);
    rb_define_method(scope_inst_methods, rb_method_name_enter_dslr_scope,
                     inst_methods_enter_dsrl_scope, 0);
    rb_define_method(scope_inst_methods, rb_method_name_exit_dslr_scope,
                     inst_methods_exit_dsrl_scope, 0);
    rb_define_method(scope_inst_methods, rb_method_name_in_split_scope,
                     inst_methods_in_split_scope, 0);
    rb_define_method(scope_inst_methods, rb_method_name_enter_split_scope,
                     inst_methods_enter_split_scope, 0);
    rb_define_method(scope_inst_methods, rb_method_name_exit_split_scope,
                     inst_methods_exit_split_scope, 0);
    rb_define_method(scope_inst_methods, rb_method_name_split_scope_depth,
                     inst_methods_split_scope_depth, 0);
    rb_define_method(scope_inst_methods, rb_method_name_in_scope,
                     inst_methods_in_scope, 1);
    rb_define_method(scope_inst_methods, rb_method_name_enter_scope,
                     inst_methods_enter_scope, 1);
    rb_define_method(scope_inst_methods, rb_method_name_exit_scope,
                     inst_methods_exit_scope, 1);

    /**
     * @module Contrast::Components::Scope
     */
    rb_define_singleton_method(scope_mod, "sweep_dead_ecs",
                               scope_mod_sweep_dead_ecs, 0);
    /* Contrast::Components::Scope#cs__with_app_scope? */
    rb_define_singleton_method(scope_mod, "cs__with_app_scope?",
                               contrast_scope_application_update, 0);

    /**
     * @module Contrast::Components::Scope::InstanceMethods
     */
    rb_define_method(scope_inst_methods, "contrast_enter_method_scopes!",
                     inst_methods_enter_method_scope, 1);
    rb_define_method(scope_inst_methods, "contrast_exit_method_scopes!",
                     inst_methods_exit_method_scope, 1);
}