library("testthat")
library("survival")
library("gnm")

tolerance <- 1E-4

test_that("Check very small Cox example with ties, but without weights",{
    test <- read.table(header=T, sep = ",", text = "
                       start, length, status, x1, x2
                       0, 5, 0, 0, 1
                       0, 4, 1, 1, 2
                       0, 3, 0, 0, 1
                       0, 2, 0, 1, 0
                       0, 2, 1, 0, 0
                       0, 2, 1, 0, 1
                       0, 1, 0, 0, 0
                       0, 1, 1, 2, 1 ")
    tolerance <- 1E-4

    gold <- coxph(Surv(length, status) ~ x1 + x2, test, ties = "breslow")

    cyclopsData <- createCyclopsData(Surv(length, status) ~ x1 + x2, data = test, modelType = "cox")
    cyclopsFit <- fitCyclopsModel(cyclopsData)

    expect_equal(coef(gold), coef(cyclopsFit), tolerance = tolerance)
    expect_equivalent(logLik(gold),logLik(cyclopsFit))

})

test_that("Check very small Cox example without ties, but with weights",{
    test <- read.table(header=T, sep = ",", text = "
                   start, length, status, x1, x2
                       0, 5,  0,0,1
                       0, 4,  1,1,2
                       0, 3,  0,0,1
                       0, 2,  0,1,0
                       0, 2,  1,0,0
                       0, 1,  0,1,0
                       0, 1,  1,2,1 ")
    weights <- c(1,2,1,2,3,2,1)

    gold <- coxph(Surv(length, status) ~ x1 + x2, test, weights = weights)

    cyclopsData <- createCyclopsData(Surv(length, status) ~ x1 + x2, data = test, modelType = "cox")
    cyclopsFit <- fitCyclopsModel(cyclopsData, weights = weights)

    expect_equal(coef(gold), coef(cyclopsFit))
    expect_equivalent(logLik(gold),logLik(cyclopsFit))
})

test_that("Check very small Cox example with ties, with weights",{
    test <- read.table(header=T, sep = ",", text = "
                       start, length, status, x1, x2
                       0, 5, 0, 0, 1
                       0, 4, 1, 1, 2
                       0, 3, 0, 0, 1
                       0, 2, 0, 1, 0
                       0, 2, 1, 0, 0
                       0, 2, 1, 0, 1
                       0, 1, 0, 0, 0
                       0, 1, 1, 2, 1 ")
    weights <- c(1,2,1,2,4,3,2,1)

    gold <- coxph(Surv(length, status) ~ x1 + x2, test, weights, ties = "breslow")

    cyclopsData <- createCyclopsData(Surv(length, status) ~ x1 + x2, data = test, modelType = "cox")
    cyclopsFit <- fitCyclopsModel(cyclopsData, weights=weights)

    expect_equal(coef(gold), coef(cyclopsFit))
    expect_equivalent(logLik(gold),logLik(cyclopsFit))

})

test_that("Check predictive log likelihood",{
    test <- read.table(header=T, sep = ",", text = "
                       start, length, event, x1, x2
                       0, 4,  1,0,0
                       0, 3,  1,2,0
                       0, 3,  0,0,1
                       0, 2,  1,0,1
                       0, 2,  1,1,1
                       0, 1,  0,1,0
                       0, 1,  1,1,0")

    gold <- coxph(Surv(length, event) ~ x1 + strata(x2), test, ties = "breslow")

    # Double the data
    test2 <- rbind(data.frame(test, index = 1:7), data.frame(test, index = 1:7))
    test2 <- test2[order(test2$index),]

    data <- createCyclopsData(Surv(length, event) ~ x1 + strata(x2), data = test2,
                              modelType = "cox")

    # Fit with the second set
    weights = rep(c(0,1), 7)
    fit <- fitCyclopsModel(data, weights = weights)

    tolerance <- 1E-4
    expect_equal(coef(fit), coef(gold), tolerance = tolerance)
    expect_equivalent(logLik(fit), logLik(gold))

    # Get predictive log likelihood of first set
    pred <- Cyclops:::.cyclopsGetNewPredictiveLogLikelihood(fit$interface, weights = 1 - weights)
    expect_equal(pred, as.numeric(logLik(gold)), tolerance)
})

test_that("Check very small Cox example with weighting", {
    test <- read.table(header=T, sep = ",", text = "
                       start, length, event, x1, x2
                       0, 4,  1,0,0
                       0, 3.5,1,2,0
                       0, 3,  0,0,1
                       0, 2.5,1,0,1
                       0, 2,  1,1,1
                       0, 1.5,0,1,0
                       0, 1,  1,1,0")

    gold <- coxph(Surv(length, event) ~ x1 + x2, test, ties = "breslow")

    # Duplicate some rows, so we can reweigh later:
    testDup <- rbind(test, test[c(1,3,4),])
    weights <- c(0.5, 1, 0.5, 0.5, 1, 1, 1, 0.5, 0.5, 0.5)

    goldDup <- coxph(Surv(length, event) ~ x1 + x2, testDup, weights = weights, ties = "breslow")

    expect_equal(coef(gold), coef(goldDup))

    cyclopsData <- createCyclopsData(Surv(length, event) ~ x1 + x2, data = testDup, modelType = "cox")
    cyclopsFit <- fitCyclopsModel(cyclopsData, weights = weights)

    expect_equal(coef(gold), coef(cyclopsFit), tolerance = 1E-6)
    expect_equal(coef(goldDup), coef(cyclopsFit), tolerance = 1E-6)

    # Weights with sparse and indicator values
    cyclopsData4 <- createCyclopsData(Surv(length, event) ~ 1, sparseFormula = ~ x1,
                                      indicatorFormula = ~ x2,
                                      data = testDup, modelType = "cox",
                                      weights = weights)
    cyclopsFit4 <- fitCyclopsModel(cyclopsData4)
    expect_equal(coef(goldDup), coef(cyclopsFit4), tolerance = 0.000001)

})

test_that("Multi-core weights", {
    test <- read.table(header=T, sep = ",", text = "
                       start, length, status, x1, x2
                       0, 5,  0,0,1
                       0, 4,  1,1,2
                       0, 3,  0,0,1
                       0, 2,  0,1,0
                       0, 2,  1,0,0
                       0, 1,  0,1,0
                       0, 1,  1,2,1 ")
    weights <- c(1,2,1,2,3,2,1)

    cyclopsData <- createCyclopsData(Surv(length, status) ~ x1 + x2, data = test, modelType = "cox")
    cyclopsFit <- fitCyclopsModel(cyclopsData, weights = weights,
                                  control = createControl(noiseLevel = "silent"))
    ci1 <- confint(cyclopsFit, "x1")
    cyclopsFit <- fitCyclopsModel(cyclopsData, weights = weights,
                                  control = createControl(threads = 2,
                                                          noiseLevel = "silent"))
    ci2 <- confint(cyclopsFit, "x1")

    expect_equal(ci1, ci2)
})

test_that("Small Bernoulli dense regression with weighting", {
    binomial_bid <- c(1,5,10,20,30,40,50,75,100,150,200)
    binomial_n <- c(31,29,27,25,23,21,19,17,15,15,15)
    binomial_y <- c(0,3,6,7,9,13,17,12,11,14,13)

    log_bid <- log(c(rep(rep(binomial_bid, binomial_n - binomial_y)), rep(binomial_bid, binomial_y)))
    y <- c(rep(0, sum(binomial_n - binomial_y)), rep(1, sum(binomial_y)))

    weights <- as.numeric(gl(5,1,length(y)))

    tolerance <- 1E-4

    glmFit <- glm(y ~ log_bid, family = binomial(), weights = weights) # gold standard

    dataPtrD <- createCyclopsData(y ~ log_bid, modelType = "lr")
    cyclopsFitD <- fitCyclopsModel(dataPtrD, prior = createPrior("none"),
                                   control = createControl(noiseLevel = "silent"), weights = weights)

    expect_equal(coef(cyclopsFitD), coef(glmFit), tolerance = tolerance)
    expect_equal(cyclopsFitD$log_likelihood, logLik(glmFit)[[1]], tolerance = tolerance)
    expect_equal(confint(cyclopsFitD, c(1:2))[,2:3], confint(glmFit, c(1:2)), tolerance = tolerance)
    expect_equal(predict(cyclopsFitD), predict(glmFit, type = "response"), tolerance = tolerance)
})

test_that("Small Bernoulli dense regression with zero-weights", {
    binomial_bid <- c(1,5,10,20,30,40,50,75,100,150,200)
    binomial_n <- c(31,29,27,25,23,21,19,17,15,15,15)
    binomial_y <- c(0,3,6,7,9,13,17,12,11,14,13)

    log_bid <- log(c(rep(rep(binomial_bid, binomial_n - binomial_y)), rep(binomial_bid, binomial_y)))
    y <- c(rep(0, sum(binomial_n - binomial_y)), rep(1, sum(binomial_y)))

    weights <- as.numeric(gl(2,1,length(y))) - 1

    y_sub <- y[which(weights == 1)]
    log_bid_sub <- log_bid[which(weights == 1)]

    glmFit <- glm(y ~ log_bid, family = binomial(), weights = weights) # gold standard

    tolerance <- 1E-4

    data <- createCyclopsData(y ~ log_bid, modelType = "lr")
    data_sub <- createCyclopsData(y_sub ~ log_bid_sub, modelType = "lr")

    fit <- fitCyclopsModel(data, prior = createPrior("none"),
                           control = createControl(noiseLevel = "silent"),
                           weights = weights)

    fit_sub <- fitCyclopsModel(data_sub, prior = createPrior("none"),
                               control = createControl(noiseLevel = "silent"))

    expect_equal(coef(fit), coef(glmFit), tolerance = tolerance)
    expect_equal(fit$log_likelihood, logLik(glmFit)[[1]], tolerance = tolerance)
    expect_equal(predict(glmFit, type = "response"), predict(fit), tolerance = tolerance)

    expect_equal(coef(fit), coef(fit_sub), check.attributes = FALSE)
    expect_equal(fit$log_likelihood, fit_sub$log_likelihood)

    expect_equal(predict(fit)[which(weights == 1)], predict(fit_sub), check.attributes = FALSE)
})

test_that("Small Poisson dense regression with weighting", {
    dobson <- data.frame(
        counts = c(18,17,15,20,10,20,25,13,12),
        outcome = gl(3,1,9),
        treatment = gl(3,3)
    )
    tolerance <- 1E-4

    weights <- c(1,2,1,2,4,3,2,1,1)

    glmFit <- glm(counts ~ outcome + treatment, data = dobson, weights, family = poisson()) # gold standard

    dataPtrD <- createCyclopsData(counts ~ outcome + treatment, data = dobson,
                                  modelType = "pr")
    cyclopsFitD <- fitCyclopsModel(dataPtrD,
                                   prior = createPrior("none"),
                                   control = createControl(noiseLevel = "silent"), weights = weights)

    expect_equal(coef(cyclopsFitD), coef(glmFit), tolerance = tolerance)
    expect_equal(cyclopsFitD$log_likelihood, logLik(glmFit)[[1]], tolerance = tolerance)
    expect_equal(confint(cyclopsFitD, c(1:3))[,2:3], confint(glmFit, c(1:3)), tolerance = tolerance)
    expect_equal(predict(cyclopsFitD), predict(glmFit, type = "response"), tolerance = tolerance)
    expect_equal(confint(cyclopsFitD, c("(Intercept)","outcome3")), confint(cyclopsFitD, c(1,3)))

    fit <- fitCyclopsModel(dataPtrD,
                           prior = createPrior("laplace", useCrossValidation = TRUE),
                           weights = weights, warnings = FALSE,
                           control = createControl(minCVData = 1, noiseLevel = "quiet"))
})

test_that("Check conditional Poisson with cross-validation",{
    set.seed(123)
    sim <- simulateCyclopsData(nstrata=1000,
                               ncovars=10,
                               nrows=10000,
                               effectSizeSd=0.5,
                               eCovarsPerRow=2,
                               model="poisson")
    suppressWarnings(
        cyclopsData <- convertToCyclopsData(outcomes = sim$outcomes,
                                            covariates = sim$covariates,
                                            modelType = "cpr")
    )
    fit <- fitCyclopsModel(cyclopsData = cyclopsData,
                           prior = createPrior("laplace",
                                               useCrossValidation = TRUE,
                                               exclude = 1),
                           control = createControl(fold = 10,
                                                   cvRepetitions = 1,
                                                   startingVariance = 0.1,
                                                   noiseLevel = "quiet",
                                                   seed = 123))
})


test_that("Large Cox regression with weighting",{
    set.seed(123)
    sim <- simulateCyclopsData(nstrata=1000,
                               ncovars=10,
                               nrows=10000,
                               effectSizeSd=0.5,
                               eCovarsPerRow=2,
                               model="survival")
    sim$outcomes$weights <- 1/sim$outcomes$rr

    # Gold standard
    covariates <- sim$covariates
    ncovars <- max(covariates$covariateId)
    nrows <- nrow(sim$outcomes)
    m <- matrix(0,nrows,ncovars)
    for (i in 1:nrow(covariates)){
        m[covariates$rowId[i],covariates$covariateId[i]] <- 1
    }
    data <- as.data.frame(m)

    data$rowId <- 1:nrow(data)
    data <- merge(data,sim$outcomes)
    data <- data[order(data$stratumId,data$rowId),]
    formula <- as.formula(paste(c("Surv(time,y) ~ strata(stratumId)",paste("V",1:ncovars,sep="")),collapse=" + "))
    fitCoxph <- survival::coxph(formula, data = data, weights = data$weights, ties = "breslow")

    # Cyclops
    cyclopsData <- convertToCyclopsData(outcomes = sim$outcomes,
                                        covariates = sim$covariates,
                                        modelType = "cox")
    fitCyclops <- fitCyclopsModel(cyclopsData = cyclopsData)

    expect_equivalent(coef(fitCyclops), coef(fitCoxph), tolerance = tolerance)
})

# # currently broken
# test_that("Small conditional logistic regression with weighting", {
#
#     weights <- as.numeric(gl(5,1,length(infert$case)))
#
#     gold <- clogit(case ~ spontaneous + induced + strata(stratum), data=infert, weights = weights, method=c("approximate"))
#
#     dataPtr <- createCyclopsData(case ~ spontaneous + induced + strata(stratum),
#                                  data = infert,
#                                  modelType = "clr")
#
#     cyclopsFit <- fitCyclopsModel(dataPtr, prior = createPrior("none"), weights = weights)
#
#     tolerance <- 1E-4
#
#     expect_equal(coef(cyclopsFit), coef(gold), tolerance = tolerance)
#     expect_equal(cyclopsFit$log_likelihood, logLik(gold)[[1]], tolerance = tolerance)
#
#     # expect_equal(vcov(cyclopsFit), vcov(gold), tolerance = tolerance)
#     # expect_equal(aconfint(cyclopsFit), confint(gold), tolerance = tolerance)
#
#     expect_equal(confint(cyclopsFit, c(1:2), includePenalty = TRUE),
#                  confint(cyclopsFit, c(1:2), includePenalty = FALSE))
#
# })
#
# test_that("Check simple SCCS as conditional Poisson regression with weighting", {
#     #     source("helper-conditionalPoisson.R")
#     tolerance <- 1E-3
#     weights <- as.numeric(gl(5,1,38))
#     gold.cp <- gnm(event ~ exgr + agegr + offset(loginterval),
#                    family = poisson, eliminate = indiv, weights = weights,
#                    data = Cyclops::oxford)
#
#     dataPtr <- createCyclopsData(event ~ exgr + agegr + strata(indiv) + offset(loginterval),
#                                  data = Cyclops::oxford,
#                                  modelType = "cpr")
#     cyclopsFit <- fitCyclopsModel(dataPtr, weights = weights,
#                                   prior = createPrior("none"))
#
#     expect_equal(coef(cyclopsFit)[1:2], coef(gold.cp)[1:2], tolerance = tolerance)
#     # expect_equal(confint(cyclopsFit, c("exgr1","agegr2"))[,2:3],
#     # confint(gold.cp), tolerance = tolerance)
# })
#
# test_that("Check simple SCCS as SCCS with weighting", {
#     #     source("helper-conditionalPoisson.R")
#     tolerance <- 1E-6
#     weights <- as.numeric(gl(5,1,38))
#
#     gold.clogit <- clogit(event ~ exgr + agegr + strata(indiv) + offset(loginterval),weights = weights,
#                           data = Cyclops::oxford)
#
#     dataPtr <- createCyclopsData(event ~ exgr + agegr + strata(indiv), time = Cyclops::oxford$interval,
#                                  data = Cyclops::oxford,
#                                  modelType = "sccs")
#     cyclopsFit <- fitCyclopsModel(dataPtr,weights = weights,
#                                   prior = createPrior("none"))
#     expect_equivalent(logLik(cyclopsFit), logLik(gold.clogit))
#     expect_equal(coef(cyclopsFit), coef(gold.clogit), tolerance = tolerance)
# })
