library(lobstr)
library(testthat)
library(inline)

unlockEnvironment <- cfunction(signature(env = "environment"), body = '
  #define FRAME_LOCK_MASK (1<<14)
  #define FRAME_IS_LOCKED(e) (ENVFLAGS(e) & FRAME_LOCK_MASK)
  #define UNLOCK_FRAME(e) SET_ENVFLAGS(e, ENVFLAGS(e) & (~ FRAME_LOCK_MASK))

  if (TYPEOF(env) == NILSXP)
    error("use of NULL environment is defunct");
  if (TYPEOF(env) != ENVSXP)
    error("not an environment");

  UNLOCK_FRAME(env);

  // Return TRUE if unlocked; FALSE otherwise
  SEXP result = PROTECT( Rf_allocVector(LGLSXP, 1) );
  LOGICAL(result)[0] = FRAME_IS_LOCKED(env) == 0;
  UNPROTECT(1);

  return result;
')

# To make sure these tests actually work:
#   * Un-encapsulate one or more of the encapsulated functions.
#   * load_all(), or install R6, restart R, then library(R6).
#   * Run these tests. With the function(s) commented out, there should be an
#     error. With the code restored to normal, there should be no errors.
test_that("R6 objects can be instantiated even when R6 isn't loaded", {
  library(R6)
  AC <- R6Class("AC",
    portable = TRUE,
    public = list(
      x = 0,
      initialize = function() {
        self$inc_x()
        private$inc_y()
        self$incz
      },
      inc_x = function() self$x <- self$x + 1,
      inc = function(val) val + 1,
      pinc = function(val) private$priv_inc(val), # Call private inc method
      gety = function() private$y,
      z = 0
    ),
    private = list(
      y = 0,
      inc_y = function() private$y <- private$y + 1,
      priv_inc = function(val) val + 1
    ),
    active = list(
      incz = function() {
        self$z <- self$z + 1
      }
    )
  )
  BC <- R6Class("BC",
    portable = TRUE,
    inherit = AC,
    public = list(
      inc_x = function() self$x <- self$x + 2,
      inc = function(val) super$inc(val) + 20
    ),
    private = list(
      inc_y = function() private$y <- private$y + 2,
      priv_inc = function(val) super$priv_inc(val) + 20
    ),
    active = list(
      incz = function() {
        self$z <- self$z + 2
      }
    )
  )

  # Remove everything from the R6 namespace
  r6ns <- .getNamespace('R6')
  unlockEnvironment(r6ns)
  rm(list = ls(r6ns), envir = r6ns)

  # Also try unloading R6 namespace. Even this set of commands may not be enough
  # to fully unload the R6 namespace environment, because AC and BC are children
  # of the R6 namespace.
  detach('package:R6', unload = TRUE)
  expect_null(.getNamespace('R6'))
  expect_error(as.environment('package:R6'))
  expect_error(get('R6Class', inherits = TRUE))

  B <- BC$new()

  # Testing overrides
  expect_identical(B$x, 2)       # Public
  expect_identical(B$gety(), 2)  # Private
  expect_identical(B$z, 2)       # Active
  # Calling superclass methods
  expect_identical(B$inc(0), 21)
  expect_identical(B$pinc(0), 21)


  library(R6)
  # Multi-level inheritance
  CC <- R6Class("CC",
    portable = TRUE,
    inherit = BC,
    public = list(
      inc_x = function() self$x <- self$x + 3,
      inc = function(val) super$inc(val) + 300
    ),
    private = list(
      inc_y = function() private$y <- private$y + 3,
      priv_inc = function(val) super$priv_inc(val) + 300
    ),
    active = list(
      incz = function() {
        self$z <- self$z + 3
      }
    )
  )

  # Remove everything from the R6 namespace
  r6ns <- .getNamespace('R6')
  unlockEnvironment(r6ns)
  rm(list = ls(r6ns), envir = r6ns)

  # Detach and unload R6, then run the tests as usual
  detach('package:R6', unload = TRUE)
  expect_null(.getNamespace('R6'))
  expect_error(as.environment('package:R6'))
  expect_error(get('R6Class', inherits = TRUE))

  C <- CC$new()

  # Testing overrides
  expect_identical(C$x, 3)       # Public
  expect_identical(C$gety(), 3)  # Private
  expect_identical(C$z, 3)       # Active
  # Calling superclass methods (two levels)
  expect_identical(C$inc(0), 321)
  expect_identical(C$pinc(0), 321)

  # Classes
  expect_identical(class(C), c("CC", "BC", "AC", "R6"))
})



# Encapsulate R6 in new() =======================
# This set of tests requires restarting R

library(R6)
AC <- R6Class("AC",
  portable = FALSE,
  public = list(
    x = 1,
    getx = function() self$x
  )
)

BC <- R6Class("BC",
  portable = FALSE,
  inherit = AC,
  public = list(
    x = 2,
    getx = function() self$x
  )
)

save(AC, BC, file = 'test.rda')


#### Restart R ####

library(testthat)
load('test.rda')
# R6 will be loaded
expect_true("R6" %in% loadedNamespaces())

A <- AC$new()
B <- BC$new()
expect_identical(A$getx(), 1)
expect_identical(B$getx(), 2)

# Clean up
unlink('test.rda')
